トップへ(mam-mam.net/)

ExplorerやFinderのようなAndroidファイラーを作る ~Delphiソースコード集

検索:

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.