plyer icon indicating copy to clipboard operation
plyer copied to clipboard

Problem: filechooser doesn't work on android

Open leestarb opened this issue 2 years ago • 8 comments

Calling save_file() just returns None without file choose menu!

Tested on Android 7 Flyme, 11 PE, 7 (BlueStacks 4)

  • kivy 2.0.0
  • plyer 2.0.0
  • python 3.8

leestarb avatar Jan 04 '22 10:01 leestarb

It doesn't work for me either due to permission issues: logcat reports

03-01 09:19:56.581 16058 16058 I python  :    File "/home/me/kivy/ChooserApp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/stillselect/plyer/facades/storagepath.py", line 56, in get_sdcard_dir
03-01 09:19:56.581 16058 16058 I python  :    File "/home/me/kivy/ChooserApp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/stillselect/plyer/platforms/android/storagepath.py", line 31, in _get_sdcard_dir
03-01 09:19:56.581 16058 16058 I python  :  PermissionError: [Errno 13] Permission denied: '/storage'

I'm using the official example found here since it wasn't working, I've added few lines:

if platform == "android":
    from android.permissions import request_permissions, Permission
    from android.storage import app_storage_path, primary_external_storage_path

    request_permissions([
        Permission.CAMERA,
        Permission.READ_EXTERNAL_STORAGE,
        Permission.WRITE_EXTERNAL_STORAGE
    ])

and got the phone successfully asking for permission. However even after granting those permissions (and double checking it in the Android Application settings that indeed the app has permission to access the storage) the above error is returned in the logcat and the app crash.

I've tried also one by one other permission documented here namely: Permission.MANAGE_EXTERNAL_STORAGE, Permission.ACCESS_MEDIA_LOCATION, Permission.MANAGE_MEDIA none of them works the logs says Permission had no such an attribute. Any advice?

diramazioni avatar Mar 01 '22 08:03 diramazioni

possibly you are using android 10 or 11, if you are using android 10 you must enable in your manifest android:requestLegacyExternalStorage="true

if you are using android 11, you need an implementation for scope permissions

moonpyx avatar Mar 01 '22 12:03 moonpyx

I'm using android 12 on the phone... I've been able to use MDFileManager from kivymd.uix.filemanager, initially I was getting the exact same error, then I've modified the example to make the file_manager to show from android.storage.primary_external_storage_path and it worked.

    def file_manager_open(self):
        if platform == "android":
            from android.storage import primary_external_storage_path
            self.file_manager.show(primary_external_storage_path()) 

However the same trick to specify the initial path didn't work for filechooser filechooser.open_file(path=primary_external_storage_path()) My guess is that in new android versions '/storage' is not readable and storagepath.py always start from there to build the file list, so no matter what it'll always fails

diramazioni avatar Mar 01 '22 15:03 diramazioni

as of android 11 the configuration of requestLegacyExternalStorage will not be valid and for this you should implement the MANAGE_EXTERNAL_STORAGE permission, you can have more info in this link

https://developer.android.com/training/data-storage/manage-all-files

Google will take the time to review your application thoroughly if you include this permission as it is considered a high risk permission.

moonpyx avatar Mar 01 '22 16:03 moonpyx

I've tried to add android:requestLegacyExternalStorage="true" to AndroidManifest.tmpl.xml but it fails to build with AAPT: error: attribute android:requestLegacyExternalStorage not found. Implementing MANAGE_EXTERNAL_STORAGE permissions it's beyond my skills, I've just started playing with kivy/android for a quick app to process video files and make some opencv analysis, and I'm stuck at step 1: getting the file_path!

diramazioni avatar Mar 01 '22 16:03 diramazioni

you must put it at the application tag level, it would look like this:

<application android:label="@string/app_name" {% if debug %}android:debuggable="true"{% endif %} android:icon="@mipmap/icon" android:allowBackup="{{ args.allow_backup }}" {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %} {{ args.extra_manifest_application_arguments }} android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}" android:hardwareAccelerated="true" android:requestLegacyExternalStorage="true" >

in your buildozer.spec try compiling with sdk version 29

moonpyx avatar Mar 01 '22 22:03 moonpyx

...This is exactly how I've done it, however gradle fails to build with this message

[DEBUG]:   	* What went wrong:
[DEBUG]:   	Execution failed for task ':processDebugResources'.
[DEBUG]:   	> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
[DEBUG]:   	   > Android resource linking failed
[DEBUG]:   	    /home/me/kivy/ChooserApp/.buildozer/android/platform/build-armeabi-v7a/dists/stillselect__armeabi-v7a/src/main/AndroidManifest.xml:63:5-102:19: AAPT: error: attribute android:requestLegacyExternalStorage not found.

Have you seen a kivy app example implementing MANAGE_EXTERNAL_STORAGE?

diramazioni avatar Mar 02 '22 10:03 diramazioni

Hi, I have open_file() working on android, but to me it seems like save_file() is not implemented at all on Android? I have not found any documentation on this, but after looking at the android implementation of filechooser.py, it looks like it is not written yet:

    def _file_selection_dialog(self, **kwargs):
        mode = kwargs.pop('mode', None)
        if mode == 'open':
            self._open_file(**kwargs)
    #end of function

on other platforms it has an elif with save as well...

pynting avatar Aug 11 '22 15:08 pynting

#try to do this, it will allow you to have access to all directories and files in the storage.
def permissions_external_storage(self, *args):                  
    if platform == "android":
        PythonActivity = autoclass("org.kivy.android.PythonActivity")
        Environment = autoclass("android.os.Environment")
        Intent = autoclass("android.content.Intent")
        Settings = autoclass("android.provider.Settings")
        Uri = autoclass("android.net.Uri")
        if api_version > 29:
            # If you have access to the external storage, do whatever you need
            if Environment.isExternalStorageManager():
                # If you don't have access, launch a new activity to show the user the system's dialog
                # to allow access to the external storage
                pass
            else:
                try:
                    activity = mActivity.getApplicationContext()
                    uri = Uri.parse("package:" + activity.getPackageName())
                    intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101)
                except:
                    intent = Intent()
                    intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101) 

Osmar-Souza avatar Oct 13 '22 19:10 Osmar-Souza

#try to do this, it will allow you to have access to all directories and files in the storage.
def permissions_external_storage(self, *args):                  
    if platform == "android":
        PythonActivity = autoclass("org.kivy.android.PythonActivity")
        Environment = autoclass("android.os.Environment")
        Intent = autoclass("android.content.Intent")
        Settings = autoclass("android.provider.Settings")
        Uri = autoclass("android.net.Uri")
        if api_version > 29:
            # If you have access to the external storage, do whatever you need
            if Environment.isExternalStorageManager():
                # If you don't have access, launch a new activity to show the user the system's dialog
                # to allow access to the external storage
                pass
            else:
                try:
                    activity = mActivity.getApplicationContext()
                    uri = Uri.parse("package:" + activity.getPackageName())
                    intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101)
                except:
                    intent = Intent()
                    intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101) 

As an android learner, I can prove this may work

leestarb avatar Oct 14 '22 12:10 leestarb

I am interested to hear your report... it does not look like Osmar-Souza's code opens a save file activity? My solution was to use kivymd's file browser and make a crude custom "save file" class.

It would be nice to see plyer's android parts fleshed out and revamped a bit. Gives the app a professional look when it integrates nicely with android's inbuilt tools...

pynting avatar Oct 16 '22 22:10 pynting

@pynting , it feels good to be good #728

I simplified the code a lot

leestarb avatar Nov 13 '22 17:11 leestarb

Permission with mdfilemanager https://github.com/Osmar-Souza/External-storage-permission-python

Osmar-Souza avatar Nov 15 '22 00:11 Osmar-Souza

...This is exactly how I've done it, however gradle fails to build with this message

[DEBUG]:   	* What went wrong:
[DEBUG]:   	Execution failed for task ':processDebugResources'.
[DEBUG]:   	> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
[DEBUG]:   	   > Android resource linking failed
[DEBUG]:   	    /home/me/kivy/ChooserApp/.buildozer/android/platform/build-armeabi-v7a/dists/stillselect__armeabi-v7a/src/main/AndroidManifest.xml:63:5-102:19: AAPT: error: attribute android:requestLegacyExternalStorage not found.

@pynting Have you seen a kivy app example implementing MANAGE_EXTERNAL_STORAG https://github.com/Osmar-Souza/External-storage-permission-python

Osmar-Souza avatar Nov 15 '22 00:11 Osmar-Souza

Hi all, so, after a few months, has anybody had success with gaining read permissions on Android 12 not with KivyMD (I don't use it)? I can't make anything work. Plyer/filechooser works perfectly on Windows but returns nothing on Android with both read and write permissions requested and given. The app doesn't crash or shows any error, but open_file() just returns nothing, perhaps it's None or "", and nothing actually happens. I can't even get the name of any file inside the app.

antorix avatar Jan 06 '23 10:01 antorix

#try to do this, it will allow you to have access to all directories and files in the storage.
def permissions_external_storage(self, *args):                  
    if platform == "android":
        PythonActivity = autoclass("org.kivy.android.PythonActivity")
        Environment = autoclass("android.os.Environment")
        Intent = autoclass("android.content.Intent")
        Settings = autoclass("android.provider.Settings")
        Uri = autoclass("android.net.Uri")
        if api_version > 29:
            # If you have access to the external storage, do whatever you need
            if Environment.isExternalStorageManager():
                # If you don't have access, launch a new activity to show the user the system's dialog
                # to allow access to the external storage
                pass
            else:
                try:
                    activity = mActivity.getApplicationContext()
                    uri = Uri.parse("package:" + activity.getPackageName())
                    intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101)
                except:
                    intent = Intent()
                    intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
                    currentActivity = cast(
                    "android.app.Activity", PythonActivity.mActivity
                    )
                    currentActivity.startActivityForResult(intent, 101) 

As an android learner, I can prove this may work

Thanks lesstrab for providing the only working 'python only' example for enabling ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION. I've spent way too much google time looking for this. I'm a much happier camper.

dovczitter avatar Aug 27 '23 21:08 dovczitter