Android Question Strange behavior with API29 and loading file from external storage

KZero

Active Member
Licensed User
I'm using runtime permission

When I try to load an image from external storage on target API29 I get Permission denied error

the same code works fine on target API28 or less

the problem only occurs when the first install is targeting API29, if the app was installed with an older API then installed a new version with API29 no problems


B4X:
** Activity (main) Resume **
Error occurred on line: 183 (Main)
java.io.FileNotFoundException: /storage/emulated/0/1.jpg: open failed: EACCES (Permission denied)
    at libcore.io.IoBridge.open(IoBridge.java:496)
    at java.io.FileInputStream.<init>(FileInputStream.java:159)
    at anywheresoftware.b4a.objects.streams.File.OpenInput(File.java:214)
    at anywheresoftware.b4a.objects.drawable.CanvasWrapper$BitmapWrapper.initializeSampleImpl(CanvasWrapper.java:601)
    at anywheresoftware.b4a.objects.drawable.CanvasWrapper$BitmapWrapper.InitializeResize(CanvasWrapper.java:548)
    at anywheresoftware.b4a.keywords.Common.LoadBitmapResize(Common.java:1371)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:732)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:348)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:255)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:144)
    at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:1083)
    at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:1038)
    at k0.zgridview.zgridview._sv_scrollchanged(zgridview.java:669)
    at k0.zgridview.zgridview$ResumableSub_Reload.resume(zgridview.java:486)
    at anywheresoftware.b4a.keywords.Common$13.run(Common.java:1705)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:237)
    at android.app.ActivityThread.main(ActivityThread.java:8016)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
    at libcore.io.Linux.open(Native Method)
    at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
    at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
    at libcore.io.ForwardingOs.open(ForwardingOs.java:167
 

Attachments

DonManfred

Expert
Licensed User

Securing user data in external storage - Android 10 introduces a number of changes to give users more control over files in external storage and the app data within them. Apps can store their own files in their private sandboxes, but must use MediaStore to access shared media files and use the system file picker to access shared files in the new Downloads collection. Learn more here.
I guess this applies to the internal sdcard too now.
 

DonManfred

Expert
Licensed User
You can try this example. It shows on how to access the app specific Folders to which you have access without a special permission. Even on Targetsdk 29.
The files are not at the root of the sdcard though ;-)

And you should follow the suggestion not to set it to 29 for now.

But if you want to test it out of curiosity. Here you are:

I´ve added a constants Object to the Storage library (It is the 1st Library i ever published btw).

B4X:
    @ShortName("StorageConstants")
  public static final class StorageConstants {
        public static final String DIRECTORY_ALARMS =     Environment.DIRECTORY_ALARMS;
        public static final String DIRECTORY_DCIM =     Environment.DIRECTORY_DCIM;
        public static final String DIRECTORY_DOCUMENTS =     Environment.DIRECTORY_DOCUMENTS;
        public static final String DIRECTORY_DOWNLOADS =     Environment.DIRECTORY_DOWNLOADS;
        public static final String DIRECTORY_MOVIES =     Environment.DIRECTORY_MOVIES;
        public static final String DIRECTORY_MUSIC =     Environment.DIRECTORY_MUSIC;
        public static final String DIRECTORY_NOTIFICATIONS =     Environment.DIRECTORY_NOTIFICATIONS;
        public static final String DIRECTORY_PICTURES =     Environment.DIRECTORY_PICTURES;
        public static final String DIRECTORY_PODCASTS =     Environment.DIRECTORY_PODCASTS;
        public static final String DIRECTORY_RINGTONES =     Environment.DIRECTORY_RINGTONES;

    }
In the example i get the DIRECTORY_PICTURES folder and here i do create a Album named MySpecialAlbum and then i copy a file from the asssets to this folder.
At the last action i do a ListFiles on this Folder.

B4X:
    Dim myalbum As String = storage.getAppSpecificAlbumStorageDir(stc.DIRECTORY_PICTURES,"MySpecialAlbum")
    Log("MySpecialAlbum path="&myalbum)
    File.Copy(File.DirAssets,"donmanfred.png",myalbum,"donmanfred.png")

    For Each f As String In File.ListFiles(myalbum)
        Log(">"&f)
    Next
>donmanfred.png
 

Attachments

Last edited:

KZero

Active Member
Licensed User
You can try this example. It shows on how to access the app specific Folders to which you have access without a special permission. Even on Targetsdk 29.
The files are not at the root of the sdcard though ;-)

And you should follow the suggestion not to set it to 29 for now.

But if you want to test it out of curiosity. Here you are:

I´ve added a constants Object to the Storage library (It is the 1st Library i ever published btw).

B4X:
    @ShortName("StorageConstants")
  public static final class StorageConstants {
        public static final String DIRECTORY_ALARMS =     Environment.DIRECTORY_ALARMS;
        public static final String DIRECTORY_DCIM =     Environment.DIRECTORY_DCIM;
        public static final String DIRECTORY_DOCUMENTS =     Environment.DIRECTORY_DOCUMENTS;
        public static final String DIRECTORY_DOWNLOADS =     Environment.DIRECTORY_DOWNLOADS;
        public static final String DIRECTORY_MOVIES =     Environment.DIRECTORY_MOVIES;
        public static final String DIRECTORY_MUSIC =     Environment.DIRECTORY_MUSIC;
        public static final String DIRECTORY_NOTIFICATIONS =     Environment.DIRECTORY_NOTIFICATIONS;
        public static final String DIRECTORY_PICTURES =     Environment.DIRECTORY_PICTURES;
        public static final String DIRECTORY_PODCASTS =     Environment.DIRECTORY_PODCASTS;
        public static final String DIRECTORY_RINGTONES =     Environment.DIRECTORY_RINGTONES;

    }
In the example i get the DIRECTORY_PICTURES folder and here i do create a Album named MySpecialAlbum and then i copy a file from the asssets to this folder.
At the last action i do a ListFiles on this Folder.

B4X:
    Dim myalbum As String = storage.getAppSpecificAlbumStorageDir(stc.DIRECTORY_PICTURES,"MySpecialAlbum")
    Log("MySpecialAlbum path="&myalbum)
    File.Copy(File.DirAssets,"donmanfred.png",myalbum,"donmanfred.png")

    For Each f As String In File.ListFiles(myalbum)
        Log(">"&f)
    Next
Thanks for sharing this library
I will stick with API28 for now but I couldn't wait to test the library and read about the new scoped access in Android 10, this lib will be a must for the new API 🤩
 
Top