日記帳
本ページはプロモーションが含まれています
カテゴリー
Links
blog(ブログ)マスター
アンドロイドの巣
ゼロから始めるベランダ菜園
タイトル
ラジコン
2024年4月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

[Delhi11 BUG] パス情報がない OpenPictureDialog1.FileName

カテゴリー: General
2023-10-23 12:49
タイトル

執筆:2023.10.23
編集:2023.10.23


[Delhi11 BUG] パス情報がない OpenPictureDialog1.FileName

再現方法
Windows11でAndoroidデバイスを接続して、AndoroidデバイスのSDカード内のファイルを選択する

OpenPictureDialog1は利用できないフォルダも表示されるので選択後に拒否する
    LFileName := OpenPictureDialog1.Files[0];
if Pos('\',LFileName) = 0 then
begin
ShowMessage('ドライブ情報が取得できないため操作できません。');
Exit;
end;
テキストなどを編集を選択すると
AppData\Local\Microsoft\Windows\INetCache\IE\**
に飛ばされる
エクスプローラーに表示されているドライブ情報自体が実在しないフェイクみたい


アンドロイドアプリ作ったことないので急遽Windowsアプリでしのごうと数時間で作ったのに
アクセスできない つかえなーい!!
いちいちAndroidからSDカード抜かないと操作できないので、どうしよう

・却下:ネットワークドライブに移動。 一番無難かもしれないが勝手にパケ食いされると困る。
・(めんどう):Andoridアプリ作る

とりあえず、電源切ってSDカードを移動して対応しよう。

昨日の夜 頑張って作ったのにショックだ!!

根本的解決は
頑張って Andoridアプリ作るしかなさそう

新規マルチデバイスアプリケーション
ヘッダー/フッター
保存先選択

Android64ビット
スタイル Android

実行

[PAClient エラー] エラー: E7684 プログラムが見つかりません、'Z:\bin\java.exe'

はい?

これか!!
[PAClient エラー] エラー: E7684 プログラムが見つかりません、bin\java.exe

RAD Studio11でFireMonkeyアプリをビルドし、Androidデバイスへデプロイすると、”エラー: E2308 プログラムが見つかりません”というエラーが発生する

RAD Studio 11.3のインストールメニューの[追加オプション]の"Eclipse Open JDK 11"を必ず選択してください

"Eclipse Open JDK 11"をインストール

Delphi再起動

実行
---------------------------
エラー
---------------------------
Android アプリケーションを実行したりデバッグするには、Android デバイスを PC に接続するか、Android エミュレータを作成する必要があります。
詳細は、下の [ヘルプ] ボタンをクリックしてください。
---------------------------
OK ヘルプ(H)
---------------------------

[Window Title]
エラー
[Content]
RAD Studio のヘルプがインストールされていません。RAD Studio のドキュメントを再インストールしてください。

エラーが変わった!!

Universal adb driverをインストール
エミュレーターが表示されないので実機を接続

C:\Users\Public\Documents\Embarcadero\Studio\22.0\CatalogRepository\AndroidSDK-2525-22.0.48361.3236\platform-tools
エクスプローラーにピン止め!
ついでに PATHにも追加しておこう

接続したAndroid端末で承認
adb shell ls
adb kill-server

adb shell ls

Delphi11の Android64ビット 右横の更新マークをクリック!!
接続したデバイスを選択できるようになりました!!

実行♪

端末にアプリが表示されました!!!

やったね!

ともったのもつかの間。
サンプル実行。
TakePhotoFromLibraryAction1
TBitmapで帰ってくるため
・写真が一枚しか選択できない
・ファイルパスが取得できない

使えない ばかじゃないの!!!

さすが Delphi 最初のころのDelphiバージョンはコピペで超簡単がが売りだったはずだが DIY志向が強くなっている!!
https://developer.android.com/about/versions/13/features/photopicker?hl=ja#java
に書いてあるような複数選択ができません!!

Delphiだと情報が少なすぎて時間の無駄!!

選択した値を取得する方法がわからない

Linux起動して、Androidスタジオで作り直します!!
と思ったけど、
検索結果をつぎはぎして、組みなおしたら 取得できました!!

コンテンツパスはわかるがリアルパスのフォルダ情報が取得できないという問題が発生した。Androidの仕様だから無理かな。
%2F は \ なのでいいとして

作るアプリはフォルダによって動作がかわるアプリなのでちょっと困る。
解析できないパスは使えません!!とか こっちに保存しますよ!
と表示すればとりあえず解決だね.  SDカード抜き差しして使うよりははるかにまし。

例外が発生するとアプリが落ちるので try .. except で保護したほうがいい

const
PHOTO_PICKER_REQUEST_CODE = 12345; // 競合しないように適当に大きい数字を割り当てる

procedure THeaderFooterForm.Button1Click(Sender: TObject);

var
Intent: JIntent; // Androidapi.JNI.GraphicsContentViewText
begin
FMessageSubscriptionID := TMessageManager.DefaultManager.SubscribeToMessage(
TMessageResultNotification, HandleActivityMessage);
Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_PICK);
intent.setType(StringToJString('image/*'));
intent.setAction(TjIntent.JavaClass.ACTION_GET_CONTENT);
Intent.putExtra(TJIntent.JavaClass.EXTRA_ALLOW_MULTIPLE,true);
TAndroidHelper.Activity.startActivityForResult(Intent, PHOTO_PICKER_REQUEST_CODE);
// ActivityResultLauncher.launch(intent);
end;

procedure THeaderFooterForm.onActivityResult(requestCode, resultCode: Integer; data: JIntent);
var
i: integer;
procedure SetBMP(AURI: Jnet_Uri); // Jnet_Uri - Androidapi.JNI.Net
var
fileDescriptor : JFileDescriptor;
parcelFileDescriptor : JParcelFileDescriptor;
LJBitmap : JBitmap;
LBitmapSurface, LBitmapSurface2: TBitmapSurface; // FMX.Helpers.Android
r: Double;
begin
parcelFileDescriptor := TAndroidHelper.Activity.getContentResolver.
openFileDescriptor(AURI,StringToJString('r'));
fileDescriptor := parcelFileDescriptor.getFileDescriptor;
LJBitmap := TJBitmapFactory.JavaClass.decodeFileDescriptor(fileDescriptor);
LBitmapSurface := TBitmapSurface.Create;
try
// TBitmap.Assign(LJBitmap) は対応していない
// TBitmapSurface に変換する
JBitmapToSurface(LJBitmap, LBitmapSurface);
LBitmapSurface2 := TBitmapSurface.Create;
try
// Image1.Bitmap.Assign(LBitmapSurface); // 遅延しすぎで実用性なし
// 1MBくらいなら問題ない。 2MBとかになると10秒くらいかかる / 1ドットコピーしてるのかも
// 素のままだとBitmap.Assignが遅いので縮小イメージを作る
// 同じサイズだと荒れるのでImage1のサイズの2倍にしておく。
if LBitmapSurface.Width >= LBitmapSurface.Height then
r := Math.Max(500, 2 * Image1.Width) / LBitmapSurface.Width
else
r := Math.Max(500, 2 * Image1.Height) / LBitmapSurface.Height;
LBitmapSurface2.StretchFrom(LBitmapSurface,
Trunc(LBitmapSurface.Width * r),
Trunc(LBitmapSurface.Height * r));
Image1.Bitmap.Assign(LBitmapSurface2);
finally
LBitmapSurface2.Free;
end;
finally
LBitmapSurface.Free;
end;
end;
procedure DoFiles(AURI: Jnet_Uri);
begin
//TDialogService.ShowMessage(JStringToString(AURI.toString()));
//TDialogService.ShowMessage(JStringToString(AURI.getEncodedPath()));
Label1.Text := JStringToString(AURI.toString());
FFiles.Add(JStringToString(AURI.toString()));
if FFiles.Count = 1 then
SetBMP(AURI);
end;
begin
TMessageManager.DefaultManager.Unsubscribe(TMessageResultNotification, FMessageSubscriptionID);
FMessageSubscriptionID := 0;
if RequestCode = PHOTO_PICKER_REQUEST_CODE then
begin
try
FFiles.Clear;
// 1個選択の場合: data.getClipData() = nil
if data.getClipData() = nil then
begin
DoFiles(data.getData());
end
else
for i := 0 to data.getClipData().getItemCount() - 1 do
begin
DoFiles(data.getClipData().getItemAt(i).getUri());
end;
except on E: Exception do
TDialogService.ShowMessage(E.ToString());
end;
end;
end;

procedure THeaderFooterForm.HandleActivityMessage(const Sender: TObject; const M: TMessage);
begin
if M is TMessageResultNotification then
OnActivityResult(
TMessageResultNotification(M).RequestCode,
TMessageResultNotification(M).ResultCode,
TMessageResultNotification(M).Value);
end;


URIから SDカードのアドレスを返すAPIがないようだ.
野良.javaを組み込んで呼び出してもやはり無理。
Androidくそすぎる

・fstabなどからカードを探す
・FolderViewを自分で作る
をしないと実パスを得ることができないっぽい

ほとんど移植したので(保存処理だけ残ってます)。
親パス名を取得してパス付で保存するだけなのに超絶簡単なことができない。
選択ファイルを自動加工して子フォルダを作って数個自動保存するだけなのに・・・

わかったこと
Delphiのヘルプの内容が古すぎて使えない
・インストールされているのに外部からダウンロードするように記述されている。
 "C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\converters\java2op\java2op" 
・サンプルの構文やネームスペースが違う
・その他いろいろ

ファイル操作
・1個ずつ選択してURIでの操作が基本みたい
https://developer.android.com/training/data-storage/shared/documents-files?hl=ja
・どひゃーっとリアルパスでSDカードに書き込むような仕様はないみたい。

細かいことはjavaで書いてDelphiで呼び出したほうが簡単。
なので、javaからjarファイルを作る方法と
Delphiプロジェクトに取り込みをする方法を調べたほうが快適。

java2opは J とか TJとか 変数名が いい加減なので
ファイルを全選択して、リファクタリングで、変数を選択して名称の一括修正が必要

JStringToString関数は多用するかも。

TDialogService.ShowMessage(JStringToString(TJDocumentsContract.JavaClass.getDocumentId(AURI)));

TJEnvironment.JavaClass.getExternalStorageDirectory()

TJDocumentsContract.JavaClass.getDocumentId(AURI) : split(":")

場所 URI リアルパス
内部ストレージ content://com.android.externalstorage.documents/document/primary%3A getExternalStorageDirectory
SDカード content://com.android.externalstorage.documents/document/[数値-数値]%3A 不明

AUriText := JStringToString(AURI.toString());

使う端末は固定で、TJDocumentsContract.JavaClass.getDocumentId(AURI)で デコードされたパス情報は取得できるので
あとはSDカードの物理アドレスだけ。 これも1個の端末しか使わないのでハードコートすれば解決。

function JUriToRealPath(AURI: Jnet_Uri): string;
var
LJDocId: JString;
LDocId, LDocType, LPath, LUriText: String;
begin
// 解決できないときは空を返します。
Result := '';
// https://developer.android.com/training/data-storage/shared/documents-files?hl=ja
LUriText := JStringToString(AURI.toString());
if (Pos('content://com.android.externalstorage.documents/document/', LUriText) = 1) then
begin
LJDocId := TJDocumentsContract.JavaClass.getDocumentId(AURI);
LDocId := JStringToString(LJDocId);
LDocType := Copy(LDocId, 1, Pos(':', LDocId) - 1);
LPath := Copy(LDocId, Pos(':', LDocId) + 1, Length(LDocId));
if LDocType = 'primary' then
Result := JStringToString(TJEnvironment.JavaClass.getExternalStorageDirectory().toString()) + '/' + LPath
else if DirectoryExists('/storage/' + LDocType, False) then
Result := '/storage/' + LDocType + '/' + LPath
else if DirectoryExists('/mnt/' + LDocType, False) then
Result := '/mnt/' + LDocType + '/' + LPath;
end;
end;

/fstabで情報を探るのも一手だが、使う端末は固定なので、その必要はない!!
adb shell "mount" でみるとか?

あとは、保存時に拒否されるかどうかの問題。

// デバッグ保存フォルダ名の確認
TDialogService.ShowMessage('LBaseSaveDir: ' + LBaseSaveDir);
FFiles.SaveToFile(LBaseSaveDir + '/テスト書き込み.txt');
exit;

実行!
を作成できません。Operaton not permitted
あー やっぱりそう来ましたか!

MkDirは普通に動作するみたい。なぞだ!

LJFile: JFile;
LJFileOutputStream: JFileOutputStream;

LJFile := TJFile.JavaClass.init(StringToJString('/sdcard/Download/test-debug2.txt'));
LJFile.createNewFile();
LJFileOutputStream := TJFileOutputStream.JavaClass.init(LJFile);
LJFileOutputStream.write(TAndroidHelper.TBytesToTJavaArray(TEncoding.UTF8.GetBytes('OK!')));
LJFileOutputStream.close();

保存できました!!
どうやらDelphiの関数で直接、読み込み/書き込みすると 拒否られるようです。
javaで操作しよう!

ガーン
内部のユーザー領域には書き込めるが、差し込んだ SDカードには書き込めない!!

内部に移動してもいいけど 6GBくらいあるから本体をいじめたくない。
外部SDカードなら壊れても1000円だし。

検索:
sdcard write android delphi

Android 11(API レベル 30)をターゲットとし、Android 11 で追加された MANAGE_EXTERNAL_STORAGE 権限
MANAGE_EXTERNAL_STORAGE
これか!! Android10と思っていたら端末情報みたら Android11だった! いつのまにアップグレードしたのだろう?

オプション 使用する権限 シグニチャ
外部ストレージ管理


アプリのプロパティにすべてのファイルへのアクセスが増えた。
でも実行すると
ACTION_OPEN_DOCCUMENT or related APIs.

メディア管理やドキュメントの管理もチェック。

FPermissionManageExternalStorageをいれたらハングアップしたけど、
強制停止して、もう一度アプリの権限確認して、起動したら
外部SDカードに書き込み出来るようになりました!!

やったー!! アプリ完成!! 移植成功♪

よくわかりませんが、目的の動作をしてくれるようになったのでヨシとします!


あと androidでBitmap.create後にサイズ変更すると、汚染されたメモリ利用するようで、メモリに残った残像がでてくるので
きちんと 最初に clean(色指定)で全部ピクセル消去しないと情報漏洩しますよ


ファイラー以外のほかのアプリの画像一覧が表示されない
MediaStoreインデクスに登録しないといけないらしい

1. MediaStoreインデクスに登録( ContentResolver.insertで登録 )して ファイルのJUriを取得する
      LJContentValues := TJContentValues.JavaClass.init;
LJContentValues.put(TJMediaStore_MediaColumns.JavaClass.DISPLAY_NAME,
StringToJString(ExtractFileName(LContentFilename)));
LJContentValues.put(TJMediaStore_MediaColumns.JavaClass.MIME_TYPE,
StringToJString('image/jpeg'));
LJContentValues.put(TJMediaStore_MediaColumns.JavaClass.DATE_ADDED,
TJDouble.JavaClass.init(TJClock.JavaClass.systemDefaultZone.millis));

if TJBuild_VERSION.JavaClass.SDK_INT >= 29 then
begin // (Android10 API 29)
LRelativePath := ''; // 子フォルダに入れる場合は、ディレクトリの相対パス名をいれる、 (画像ファイル名は取り除く)
LJContentValues.put(StringToJString('relative_path'), StringToJString(LRelativePath));
LJContentValues.put(StringToJString('is_pending'), TJBoolean.JavaClass.TRUE);
end;

// DATA: ファイルのパスなので指定したい場合は必ず必要 : パス形式 - Unix形式[/fullpath]
LJContentValues.put(TJMediaStore_MediaColumns.JavaClass.DATA, // _data
StringToJString(LContentFilename));

if (TJBuild_VERSION.JavaClass.SDK_INT >= 29) then // (Android10 API 29)
// MediaStore.VOLUME_EXTERNAL_PRIMARY : Constant Value: "external_primary"
LVolume := 'external_primary'
else
LVolume := 'external';
// LVolume := 'xxxx-xxxx'; // SDカードの場合は数値を書いて有効にする

LContentUri := TJImages_Media.JavaClass.getContentUri(StringToJString(LVolume));
//LContentUri := TJMyAndroidUtils.JavaClass.getContentUriFromFilename(
// TAndroidHelper.Context, StringToJString(LContentFilename));

// MediaStoreインデクスに登録
LJNewURI := TAndroidHelper.ContentResolver.insert(LContentUri, LJContentValues);


2. (1) の uriを使って ファイルハンドルを作り、画像を書き込む。
LJOutputStream := TAndroidHelper.ContentResolver.openOutputStream(LJNewURI);
try
  LJOutputStream.write(TAndroidHelper.TBytesToTJavaArray(LStream.Bytes));
finally
  LJOutputStream.close();
end;

3. (Android10 API 29)以上の場合は仮登録を確定する
   procedure updateMetadata();
var
LJContentValues: JContentValues;
ret: Integer;
begin
if (TJBuild_VERSION.JavaClass.SDK_INT >= 29) And (LJNewURI <> nil) then
begin
LJContentValues := TJContentValues.JavaClass.init;
LJContentValues.put(StringToJString('is_pending'), TJBoolean.JavaClass.False);
ret := TAndroidHelper.ContentResolver.update(LJNewURI, LJContentValues, nil, nil);
ToastShow(Format('%d: Update Result for is_pending[false]', [ret]));
end;
end;

ブログ内 関連記事: [Delhi11 BUG] パス情報がない OpenPictureDialog1.FileName

 関連記事はありません。

ブログ内 関連記事: [Delhi11 BUG] パス情報がない OpenPictureDialog1.FileName

 関連記事はありません。
トラックバック
トラックバックはありません。
PR

[PR]