# HG changeset patch # User Goffi # Date 1581288449 -3600 # Node ID 71f51198478ccdc6fb54f2288dcdc82f30ddcbaa # Parent 672880661797cedf7c3331723d7840be12fdcea0 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) diff -r 672880661797 -r 71f51198478c cagou/core/menu.py --- 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): diff -r 672880661797 -r 71f51198478c cagou/core/platform_/android.py --- 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": diff -r 672880661797 -r 71f51198478c cagou/core/platform_/base.py --- 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 diff -r 672880661797 -r 71f51198478c cagou/plugins/plugin_transfer_voice.py --- 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"], }