Jean_D 1 Posted February 1, 2023 I have a Moto G6 with an 'external' SD card. Because, my device 'internal' storage is running low in space, I thought that I would attempt to write a Delphi app that would allow me to compress the content of my 'internal' Camera folder into a 'zip' archive stored on my 'external' SD card. I understand that I could use the 'built-in' Files manager app to move the files onto my 'external' SD card. But for practice purposes, I decided to try to create my own app. I am using the latest version of Delphi Alexandria (v. 11.2). I am able to access my 'internal' DCIM folder and read its content as well as to add files to a 'zip' file. However, I am unable to 'force' the saving of my 'zip' file to my 'external' SD card. Any input would be greatly appreciated. Share this post Link to post
programmerdelphi2k 237 Posted February 1, 2023 (edited) Read this at first: https://developer.android.com/training/data-storage/manage-all-files https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE https://docwiki.embarcadero.com/RADStudio/Sydney/en/Uses_Permissions https://docwiki.embarcadero.com/RADStudio/Sydney/en/Standard_RTL_Path_Functions_across_the_Supported_Target_Platforms https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Zip.TZipFile ( not so good, but it's enough) Edited February 1, 2023 by programmerdelphi2k Share this post Link to post
Remy Lebeau 1397 Posted February 1, 2023 1 hour ago, Jean_D said: However, I am unable to 'force' the saving of my 'zip' file to my 'external' SD card. What problem are you having with it, exactly? Can you be more specific? What does your code look like that is not working? Did you grant permission to your app to access the external SD card? Do you have code that prompts the user if permission has not been granted yet? Share this post Link to post
Jean_D 1 Posted February 1, 2023 1 hour ago, Remy Lebeau said: What problem are you having with it, exactly? Can you be more specific? What does your code look like that is not working? Did you grant permission to your app to access the external SD card? Do you have code that prompts the user if permission has not been granted yet? Did you grant permission,? Do you have code that prompts the user if permission has not been granted yet? I believe that I do: procedure TForm_Main.request_Result(const aPermissions: TClassicStringDynArray; const aGrantResults: TClassicPermissionStatusDynArray); begin v_PermissionGranted := ((Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted)); end; procedure TForm_Main.request_Permissions(aPermission: String); begin PermissionsService.RequestPermissions([aPermission], request_Result); end; v_PermissionGranted := PermissionsService.IsPermissionGranted(JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)); if not v_PermissionGranted then request_Permissions(JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)); In terms of 'MANAGE_EXTERNAL_STORAGE,' it is only defined within in the manifest file. Is that not enough? My issue is that, when I execute the following code, the 'zip' file is not created on the SD card. iZipFile := TZipFile.Create; iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite); Share this post Link to post
Remy Lebeau 1397 Posted February 2, 2023 (edited) 18 hours ago, Jean_D said: I believe that I do: In case you have to call RequestPermissions(), are you waiting for request_Result() to be called before you attempt to access the SD card? RequestPermissions() is asynchronous, so your code logic should look something like this: procedure TForm_Main.SaveZipFile; var iZipFile: TZipFile; begin iZipFile := TZipFile.Create; try iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite); //... finally iZipFile.Free; end; end; procedure TForm_Main.request_Result(const aPermissions: TClassicStringDynArray; const aGrantResults: TClassicPermissionStatusDynArray); begin if (Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted) then SaveZipFile; end; procedure TForm_Main.request_Permissions(aPermission: String); begin PermissionsService.RequestPermissions([aPermission], request_Result); end; ... sWriteExternalStorage := JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE); if PermissionsService.IsPermissionGranted(sWriteExternalStorage) then SaveZipFile else request_Permissions(sWriteExternalStorage); Though, I would probably want to generalize that a bit further, eg: procedure TForm_Main.SaveZipFile; var iZipFile: TZipFile; begin iZipFile := TZipFile.Create; try iZipFile.Open(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetSharedDocumentsPath, 'Folder-Content.zip'), zmWrite); // ... finally iZipFile.Free; end; end; procedure TForm_Main.doTask_withPermission(const aPermission: String; AProc: TProc); begin if PermissionsService.IsPermissionGranted(aPermission) then begin AProc; end else begin PermissionsService.RequestPermissions([aPermission], procedure(const aPermissions: TArray<String>; const aGrantResults: TArray<TPermissionStatus>); begin if (Length(aGrantResults) = 1) and (aGrantResults[0] = TPermissionStatus.Granted) then begin AProc; end; end ); end; end; ... doTask_withPermission( JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE), SaveZipFile ); Quote In terms of 'MANAGE_EXTERNAL_STORAGE,' it is only defined within in the manifest file. Is that not enough? No. It is a permission like any other. The user has to grant access to it. Quote My issue is that, when I execute the following code, the 'zip' file is not created on the SD card. Are you getting any kind of runtime error about the file not being created? If not, have you tried looking through the device to see if the file is getting created somewhere else that you are not expecting? On Android, TPath.GetSharedDocumentsPath() attempts 2 different folder paths - first, it tries to retrieve the user's Documents folder, and if that fails then it retrieves the user's Downloads folder and replaces the last subfolder in the path with a hard-coded 'Documents'. Edited February 2, 2023 by Remy Lebeau Share this post Link to post
Jean_D 1 Posted February 3, 2023 Quote In case you have to call RequestPermissions(), are you waiting for request_Result()? Yes, I was. But your solution is more elegant. Quote No. It (MANAGE_EXTERNAL_STORAGE) is a permission like any other. The user has to grant access to it. Since it is not defined within the 'Androidapi.JNI.Os.pas' file, I didn't think that I had to follow the same process as I did for 'WRITE_EXTERNAL_STORAGE'. How would I do that. Quote Are you getting any kind of runtime error about the file not being created? No, I am not getting any error. The application runs 'properly.' The issue is that, instead of storing the 'zip' file within the external SD card, it saves it within the internal 'Documents' folder. Since I am running out of space 'internally,' the compression does not complete. Share this post Link to post
Remy Lebeau 1397 Posted February 3, 2023 16 hours ago, Jean_D said: Since [MANAGE_EXTERNAL_STORAGE] is not defined within the 'Androidapi.JNI.Os.pas' file, I didn't think that I had to follow the same process as I did for 'WRITE_EXTERNAL_STORAGE'. How would I do that. Permissions are just string values. The string value of EXTERNAL_MANAGE_STORAGE is 'android.permission.MANAGE_EXTERNAL_STORAGE'. However, granting that permission appears to be different than granting other permissions, per Android's documentation: Quote An app can request All files access from the user by doing the following: Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest. Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app: Allow access to manage all files. To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager(). 16 hours ago, Jean_D said: No, I am not getting any error. The application runs 'properly.' The issue is that, instead of storing the 'zip' file within the external SD card, it saves it within the internal 'Documents' folder. In earlier versions, TPath.GetSharedDocumentsPath() used to call Android's Context.getExternalFilesDir() method, but nowadays it calls Environment.getExternalStoragePublicDirectory() instead. Maybe Google changed where that latter API's folder is located, compared to the former API? I don't know. One thing to note from the Context.getExternalFilesDir() documentation: Quote If a shared storage device is emulated (as determined by Environment#isExternalStorageEmulated(File)), it's contents are backed by a private user data partition, which means there is little benefit to storing data here instead of the private directories returned by getFilesDir(), etc. So, you might want to check if your device is emulating shared storage or not. Also, Delphi's Androidapi.IOUtils unit has a public GetExternalDocumentsDir() function, which calls Android's Context.getExternalFilesDir() method. Maybe try using that function instead of using TPath.GetSharedDocumentsPath(). Share this post Link to post
radekc 1 Posted November 25, 2023 IF you want use EXTERNAL_MANAGE_STORAGE and use same code as before (on Android 13 required, introduced in Android 11), your user must enable all access, you can invoke dialog with this code (working code based on Kastri library) class function TMainForm.NeedsAllFilesAccessPermission: Boolean; begin Result := TOSVersion.Check(11) and not TJEnvironment.JavaClass.isExternalStorageManager; end; class function TMainForm.ShowAllFilesAccessPermissionSettings: Boolean; var LIntent: JIntent; LUri: Jnet_Uri; LAction: JString; begin Result := False; if NeedsAllFilesAccessPermission then begin LUri := TJnet_Uri.JavaClass.fromParts(StringToJString('package'), TAndroidHelper.Context.getPackageName, nil); LAction := StringToJString('android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION'); LIntent := TJIntent.JavaClass.init(LAction, LUri); TAndroidHelper.Context.startActivity(LIntent); Result := True; end; end; Share this post Link to post