changeset 400:71f51198478c

android: handle runtime permissions: - some mandatory permissions are requested on Cagou start, Cagou won't start at all and display a warning message if they are not granted (we request 5 times before showing the warning) - transfer plugin can now use "android_permissions" in plugin_info, to indicate what is necessary. The permissions will then be requested, and the plugin widget won't be shown if they are not granted (and a warning not will then be displayed)
author Goffi <goffi@goffi.org>
date Sun, 09 Feb 2020 23:47:29 +0100
parents 672880661797
children 788e05d1e2bf
files cagou/core/menu.py cagou/core/platform_/android.py cagou/core/platform_/base.py cagou/plugins/plugin_transfer_voice.py
diffstat 4 files changed, 108 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/cagou/core/menu.py	Sun Feb 09 23:47:29 2020 +0100
+++ b/cagou/core/menu.py	Sun Feb 09 23:47:29 2020 +0100
@@ -227,28 +227,42 @@
                 )
             self.items_layout.add_widget(item)
 
+    def _onTransferCb(self, file_path, external, wid_cont, cleaning_cb=None):
+        if not external:
+            wid = wid_cont[0]
+            self._closeUI(wid)
+        self.callback(
+            file_path,
+            transfer_type = (C.TRANSFER_UPLOAD
+                if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND),
+            cleaning_cb=cleaning_cb,
+        )
+
+    def _check_plugin_permissions_cb(self, plug_info):
+        external = plug_info.get('external', False)
+        wid_cont = []
+        wid_cont.append(plug_info['factory'](
+            plug_info,
+            partial(self._onTransferCb, external=external, wid_cont=wid_cont),
+            self.cancel_cb,
+            self.profiles))
+        if not external:
+            G.host.showExtraUI(wid_cont[0])
+
     def do_callback(self, plug_info):
         self.parent.remove_widget(self)
         if self.callback is None:
             log.warning("TransferMenu callback is not set")
         else:
-            wid = None
-            external = plug_info.get('external', False)
-            def onTransferCb(file_path, cleaning_cb=None):
-                if not external:
-                    self._closeUI(wid)
-                self.callback(
-                    file_path,
-                    transfer_type = (C.TRANSFER_UPLOAD
-                        if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND),
-                    cleaning_cb=cleaning_cb,
-                )
-            wid = plug_info['factory'](plug_info,
-                                       onTransferCb,
-                                       self.cancel_cb,
-                                       self.profiles)
-            if not external:
-                G.host.showExtraUI(wid)
+            G.local_platform.check_plugin_permissions(
+                plug_info,
+                callback=partial(self._check_plugin_permissions_cb, plug_info),
+                errback=lambda: G.host.addNote(
+                    _("permission refused"),
+                    _("this transfer menu can't be used if you refuse the requested "
+                      "permission"),
+                    C.XMLUI_DATA_LVL_WARNING)
+            )
 
 
 class EntitiesSelectorMenu(SideMenu, FilterBehavior):
--- a/cagou/core/platform_/android.py	Sun Feb 09 23:47:29 2020 +0100
+++ b/cagou/core/platform_/android.py	Sun Feb 09 23:47:29 2020 +0100
@@ -27,18 +27,26 @@
 import mimetypes
 from jnius import autoclass, cast
 from android import activity
+from android.permissions import request_permissions, Permission
+from kivy.clock import Clock
+from kivy.uix.label import Label
 from sat.core.i18n import _
 from sat.core import log as logging
 from sat_frontends.tools import jid
 from cagou.core.constants import Const as C
 from cagou.core import dialog
 from cagou import G
-from kivy.clock import Clock
 from .base import Platform as BasePlatform
 
 
 log = logging.getLogger(__name__)
 
+# permission that are necessary to have Cagou running properly
+PERMISSION_MANDATORY = [
+    Permission.READ_EXTERNAL_STORAGE,
+    Permission.WRITE_EXTERNAL_STORAGE,
+]
+
 service = autoclass('org.salutatoi.cagou.ServiceBackend')
 PythonActivity = autoclass('org.kivy.android.PythonActivity')
 mActivity = PythonActivity.mActivity
@@ -109,11 +117,56 @@
         log.error(f"Error while getting profile to autoconnect: {failure_}")
         G.host.postInit()
 
-    def do_postInit(self):
-        G.host.bridge.profileAutoconnectGet(
+    def _show_perm_warning(self, permissions):
+        root_wid = G.host.app.root
+        perm_warning = Label(
+            size_hint=(1, 1),
+            text_size=(root_wid.width, root_wid.height),
+            font_size='22sp',
+            bold=True,
+            color=(0.67, 0, 0, 1),
+            halign='center',
+            valign='center',
+            text=_(
+            "Requested permissions are mandatory to run Cagou, if you don't "
+            "accept them, Cagou can't run properly. Please accept following "
+            "permissions, or set them in Android settings for Cagou:\n"
+            "{permissions}\n\nCagou will be closed in 20 s").format(
+                permissions='\n'.join(p.split('.')[-1] for p in permissions)))
+        root_wid.clear_widgets()
+        root_wid.add_widget(perm_warning)
+        Clock.schedule_once(lambda *args: G.host.app.stop(), 20)
+
+    def permission_cb(self, permissions, grant_results):
+        if not all(grant_results):
+            # we keep asking until they are accepted, as we can't run properly
+            # without them
+            # TODO: a message explaining why permission is needed should be printed
+            # TODO: the storage permission is mainly used to set download_dir, we should
+            #   be able to run Cagou without it.
+            if not hasattr(self, 'perms_counter'):
+                self.perms_counter = 0
+            self.perms_counter += 1
+            if self.perms_counter > 5:
+                Clock.schedule_once(
+                    lambda *args: self._show_perm_warning(permissions),
+                    0)
+                return
+
+            perm_dict = dict(zip(permissions, grant_results))
+            log.warning(
+                f"not all mandatory permissions are granted, requesting again: "
+                f"{perm_dict}")
+            request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
+            return
+
+        Clock.schedule_once(lambda *args: G.host.bridge.profileAutoconnectGet(
             callback=self.profileAutoconnectGetCb,
-            errback=self.profileAutoconnectGetEb
-        )
+            errback=self.profileAutoconnectGetEb),
+            0)
+
+    def do_postInit(self):
+        request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
         return False
 
     def onProfilePlugged(self, profile):
@@ -298,6 +351,20 @@
 
         log.debug(msg)
 
+    def check_plugin_permissions(self, plug_info, callback, errback):
+        perms = plug_info.get("android_permissons")
+        if not perms:
+            callback()
+        perms = [f"android.permission.{p}" if '.' not in p else p for p in perms]
+
+        def request_permissions_cb(permissions, granted):
+            if all(granted):
+                Clock.schedule_once(lambda *args: callback())
+            else:
+                Clock.schedule_once(lambda *args: errback())
+
+        request_permissions(perms, callback=request_permissions_cb)
+
     def open_url(self, url, wid=None):
         parsed_url = urlparse(url)
         if parsed_url.scheme == "geo":
--- a/cagou/core/platform_/base.py	Sun Feb 09 23:47:29 2020 +0100
+++ b/cagou/core/platform_/base.py	Sun Feb 09 23:47:29 2020 +0100
@@ -75,6 +75,10 @@
     def updateParamsExtra(self, extra):
         pass
 
+    def check_plugin_permissions(self, plug_info, callback, errback):
+        """Check that plugin permissions for this platform are granted"""
+        callback()
+
     def open_url(self, url, wid=None):
         """Open an URL in the way appropriate for the platform
 
--- a/cagou/plugins/plugin_transfer_voice.py	Sun Feb 09 23:47:29 2020 +0100
+++ b/cagou/plugins/plugin_transfer_voice.py	Sun Feb 09 23:47:29 2020 +0100
@@ -36,6 +36,7 @@
     "platforms": ["android"],
     "description": _("transmit a voice record"),
     "icon_medium": "{media}/icons/muchoslava/png/micro_off_50.png",
+    "android_permissons": ["RECORD_AUDIO"],
 }