ExplorerやFinderのようなAndroidファイラーを作る ~Delphiソースコード集
0.完成スナップショット
以下のようなアプリケーションを作成します。※Android11(API Level30)以降は「ダウンロード」ディレクトリへの アクセス権限が無くなったようです
1.フォームの作成
FMXフレームワークで以下のようにフォームにコンポーネントを配置します。
プロジェクトを「・・・\Project\MamExplorer\MamExplorer.dproj」に保存したとします。
2.AndroidManifest.template.xmlの編集
AndroidManifest.template.xmlを編集して既存の </application> の行の手前(上)に以下を追加する。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="%package%.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/>
</provider>
3.provider_paths.xmlファイルの作成
プロジェクトフォルダ内(・・・\Project\MamExplorer\)に provider_paths.xml ファイルを作成し以下の内容で保存する
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
4.「provider_paths.xml」ファイルの配置
「プロジェクト」⇒「配置」をクリックし、「ファイルの追加」ボタンをクリックして プロジェクトフォルダ内の「provider_paths.xml」ファイルを選択する。リモートパスを「res\xml\」に設定する。
5.権限設定
「プロジェクト」⇒「オプション」をクリックし、「アプリケーション」⇒「使用する権限」を選択する。・外部ストレージへの読み取り
・外部ストレージへの書き込み
・Request install packages
にチェックを入れてtrueにする。
6.ソースコード
ソースコードの殆どは、権限設定とセキュリティ回避です。
unit UMamExplorer;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
FMX.ListView, FMX.Controls.Presentation, FMX.StdCtrls, System.ImageList,
FMX.ImgList, FMX.ListBox
,Androidapi.JNI.JavaTypes ,System.IOUtils,System.Permissions,FMX.Platform;
type
TF_MamExplorer = class(TForm)
Panel1: TPanel;
ListView1: TListView;
ImageList1: TImageList;
StyleBook1: TStyleBook;
Panel2: TPanel;
Label1: TLabel;
CB_RootPath: TComboBox;
Panel3: TPanel;
LPath: TLabel;
Label2: TLabel;
Panel4: TPanel;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure CB_RootPathChange(Sender: TObject);
procedure ListView1ItemClick(const Sender: TObject;
const AItem: TListViewItem);
procedure Button1Click(Sender: TObject);
procedure FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
private
{ private 宣言 }
rPath:array[0..5] of String;
sPath:array[0..5] of String;
function getDirName(path: string): string;
procedure ShowDirFile(path: String);
procedure PermissionRequestResult(
Sender: TObject; const APermissions: TArray<string>;
const AGrantResults: TArray<TPermissionStatus>);
function AppEvent(iAppEvent: TApplicationEvent;
iContext: TObject): Boolean;
procedure RequestPermissions();
public
{ public 宣言 }
end;
function GetFileSize(const fName: string): Int64;
var
F_MamExplorer: TF_MamExplorer;
implementation
{$R *.fmx}
uses System.StrUtils
,Androidapi.JNI.GraphicsContentViewText
,AndroidAPI.Helpers ,Androidapi.JNI.Webkit ,Androidapi.JNI.Support
,Androidapi.JNI.Net ,Androidapi.JNI.Os ,FMX.DialogService;
function GetFileSize(const fName: string): Int64;
//ファイルサイズの取得
var strm: TFileStream;
begin
strm := TFileStream.Create(fName, fmOpenRead or fmShareDenyNone);
try
Result := strm.Size;
finally
strm.DisposeOf;
end;
end;
function TF_MamExplorer.AppEvent(iAppEvent: TApplicationEvent;
iContext: TObject): Boolean;
begin
Result:=False;
case iAppEvent of
TApplicationEvent.BecameActive:
begin
//focus取得時
end;
TApplicationEvent.WillBecomeInactive:
begin
//focus喪失時
end;
end;
end;
procedure TF_MamExplorer.Button1Click(Sender: TObject);
//上位ディレクトリへ ボタンを押したときの処理
var path:string;
begin
if LPath.Text=TPath.DirectorySeparatorChar then exit;
path:=getDirName(LPath.Text);
path:=LeftStr(LPath.Text,Length(LPath.Text)-Length(Path)-1);
if path='' then path:=TPath.DirectorySeparatorChar;
ShowDirFile(path);
end;
procedure TF_MamExplorer.CB_RootPathChange(Sender: TObject);
begin
ShowDirFile(TPath.DirectorySeparatorChar);
end;
procedure TF_MamExplorer.FormCreate(Sender: TObject);
var i:integer;
APPEventService:IFMXApplicationEventService;
begin
rPath[0]:=System.IOUtils.TPath.GetSharedDownloadsPath;
rPath[1]:=System.IOUtils.TPath.GetSharedMoviesPath;
rPath[2]:=System.IOUtils.TPath.GetSharedMusicPath;
rPath[3]:=System.IOUtils.TPath.GetSharedCameraPath;
rPath[4]:=System.IOUtils.TPath.GetSharedPicturesPath;
rPath[5]:=System.IOUtils.TPath.GetSharedRingtonesPath;
sPath[0]:='ダウンロード';
sPath[1]:='動画';
sPath[2]:='音楽';
sPath[3]:='カメラ';
sPath[4]:='画像';
sPath[5]:='着信音';
//ルートディレクトリ選択用コンボボックスの設定
CB_RootPath.Items.Clear;
for i := 0 to Length(sPath)-1 do
CB_RootPath.Items.Add(sPath[i]);
CB_RootPath.ItemIndex:=0;
if TPlatformServices.Current.SupportsPlatformService(
IFMXApplicationEventService) then
begin
try
APPEventService:=IFMXApplicationEventService(
TPlatformServices.Current.GetPlatformService(
IFMXApplicationEventService
)
);
except
APPEventService:=nil;
end;
end;
if (APPEventService<>nil) then
APPEventService.SetApplicationEventHandler(AppEvent);
RequestPermissions();
end;
procedure TF_MamExplorer.FormKeyUp(Sender: TObject; var Key: Word;
var KeyChar: Char; Shift: TShiftState);
begin
if Key = vkHardwareBack then Key := 0;
end;
function TF_MamExplorer.getDirName(path: string): string;
var i:integer;
begin
path:=ReverseString(path);
i:=Pos(TPath.DirectorySeparatorChar, path);
if i>0 then
path:=LeftStr(path,i-1)
else
path:='';
result:=ReverseString(path);
end;
procedure TF_MamExplorer.ListView1ItemClick(const Sender: TObject;
const AItem: TListViewItem);
var intent:JIntent;
ext:String; //拡張子
mime:JString; //MIMEタイプ
fullpath:String;
javafile:JFile;
Auth:JString;
Provider:Jnet_Uri;
begin
//ファイルが選択された場合
if AItem.ImageIndex=1 then
begin
//フルパスの取得
if LPath.Text=TPath.DirectorySeparatorChar then
fullpath:=rPath[CB_RootPath.ItemIndex]+LPath.Text+AItem.Text
else
fullpath:=rPath[CB_RootPath.ItemIndex]+LPath.Text+
TPath.DirectorySeparatorChar+AItem.Text;
Intent:=TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW);
//拡張子を取得する
ext:=LowerCase(StringReplace(TPath.GetExtension(AItem.Text),'.','',[]));
//MIMEタイプを取得する
mime:=TJMimeTypeMap.JavaClass.getSingleton.getMimeTypeFromExtension(
StringToJString(ext)
);
if mime=nil then mime := StringToJString('*/*');
//インテントにフラグを設定する
if ext='apk' then
intent.addFlags(
TJIntent.JavaClass.FLAG_GRANT_WRITE_URI_PERMISSION or
TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION or
TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK
)
else
intent.addFlags(
TJIntent.JavaClass.FLAG_GRANT_WRITE_URI_PERMISSION or
TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION
);
javafile:=TJFile.JavaClass.init(StringToJString(fullpath));
Auth := StringToJString(
JStringToString(
TAndroidHelper.Context.getApplicationContext.getPackageName
) + '.fileprovider'
);
Provider:=TJFileProvider.JavaClass.getUriForFile(
TAndroidHelper.Context, Auth, javafile
);
intent.setDataAndType(Provider, mime);
TAndroidHelper.Activity.startActivity(Intent);
end
else
begin
if LPath.Text=TPath.DirectorySeparatorChar then
LPath.Text:=AItem.Text
else
LPath.Text:=LPath.Text+AItem.Text;
ShowDirFile(LPath.Text);
end;
end;
procedure TF_MamExplorer.PermissionRequestResult(Sender: TObject;
const APermissions: TArray<string>;
const AGrantResults: TArray<TPermissionStatus>);
begin
//ストレージ書き込みの権限があるか
if (AGrantResults[0]<>TPermissionStatus.Granted) then
begin
//権限がない場合
//「□今後は表示しない」チェックボックスにチェックが入っているか
if (TJActivityCompat.JavaClass.shouldShowRequestPermissionRationale(
TAndroidHelper.Activity,
TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)) then
begin
//「□今後は表示しない」チェックボックスにチェックが入っていない場合
// 非同期でダイアログを表示して説明と許可を要求
TDialogService.MessageDialog(
'許可しないとアプリが動作しません。',TMsgDlgType.mtInformation,
[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOk,0,
procedure (const AResult: TModalResult)
begin
//2回目以降は「□今後は表示しない」チェックボックスが表示される
RequestPermissions();
end);
end
else
begin
//「□今後は表示しない」チェックボックスにチェックが入っている
TDialogService.MessageDialog(
'権限が無いため終了します。「設定⇒アプリと通知」から権限を設定してください。',
TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK,0,
procedure(const AResult:TModalResult)
begin
Application.Terminate;
end
);
end;
end
else
begin
//パスの初期化
ShowDirFile(TPath.DirectorySeparatorChar);
end;
end;
procedure TF_MamExplorer.RequestPermissions;
var FPmsWriteExternalStorage:string;
begin
FPmsWriteExternalStorage :=
JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
PermissionsService.RequestPermissions(
[FPmsWriteExternalStorage],PermissionRequestResult);
end;
procedure TF_MamExplorer.ShowDirFile(path: String);
var
pathList,filesList:TStringDynArray;
strPath,strFile:string;
lvi:TListViewItem;
RootPath:String;
DateTime : TDateTime;
Size:Int64;
begin
ListView1.Items.Clear;
RootPath:=rPath[CB_RootPath.ItemIndex];
LPath.Text:=path;
//ディレクトリの探索
try
pathList:=TDirectory.GetDirectories(RootPath+path);
for strPath in pathList do
begin
lvi:=ListView1.Items.add;
lvi.Text:=TPath.DirectorySeparatorChar+getDirName(strPath);
lvi.ImageIndex:=0;
end;
except
end;
//ファイルの探索
try
filesList:=TDirectory.GetFiles(RootPath+path,'*');
for strFile in filesList do
begin
lvi:=ListView1.Items.Add;
lvi.Text:=getDirName(strFile);
lvi.ImageIndex:=1;
//ファイルの更新日時を取得
DateTime:=TFile.GetLastWriteTime(strFile);
//ファイルのサイズを取得
Size:=GetFileSize(strFile);
lvi.Detail:=FormatDateTime('yyyy/mm/dd hh:nn:ss',DateTime)+
' '+Inttostr(Size div 1024)+'KB';
end;
except
end;
end;
end.
