comparison cagou/core/platform_/android.py @ 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 a5457241c17f
children 788e05d1e2bf
comparison
equal deleted inserted replaced
399:672880661797 400:71f51198478c
25 from pathlib import Path 25 from pathlib import Path
26 import shutil 26 import shutil
27 import mimetypes 27 import mimetypes
28 from jnius import autoclass, cast 28 from jnius import autoclass, cast
29 from android import activity 29 from android import activity
30 from android.permissions import request_permissions, Permission
31 from kivy.clock import Clock
32 from kivy.uix.label import Label
30 from sat.core.i18n import _ 33 from sat.core.i18n import _
31 from sat.core import log as logging 34 from sat.core import log as logging
32 from sat_frontends.tools import jid 35 from sat_frontends.tools import jid
33 from cagou.core.constants import Const as C 36 from cagou.core.constants import Const as C
34 from cagou.core import dialog 37 from cagou.core import dialog
35 from cagou import G 38 from cagou import G
36 from kivy.clock import Clock
37 from .base import Platform as BasePlatform 39 from .base import Platform as BasePlatform
38 40
39 41
40 log = logging.getLogger(__name__) 42 log = logging.getLogger(__name__)
43
44 # permission that are necessary to have Cagou running properly
45 PERMISSION_MANDATORY = [
46 Permission.READ_EXTERNAL_STORAGE,
47 Permission.WRITE_EXTERNAL_STORAGE,
48 ]
41 49
42 service = autoclass('org.salutatoi.cagou.ServiceBackend') 50 service = autoclass('org.salutatoi.cagou.ServiceBackend')
43 PythonActivity = autoclass('org.kivy.android.PythonActivity') 51 PythonActivity = autoclass('org.kivy.android.PythonActivity')
44 mActivity = PythonActivity.mActivity 52 mActivity = PythonActivity.mActivity
45 Intent = autoclass('android.content.Intent') 53 Intent = autoclass('android.content.Intent')
107 115
108 def profileAutoconnectGetEb(self, failure_): 116 def profileAutoconnectGetEb(self, failure_):
109 log.error(f"Error while getting profile to autoconnect: {failure_}") 117 log.error(f"Error while getting profile to autoconnect: {failure_}")
110 G.host.postInit() 118 G.host.postInit()
111 119
120 def _show_perm_warning(self, permissions):
121 root_wid = G.host.app.root
122 perm_warning = Label(
123 size_hint=(1, 1),
124 text_size=(root_wid.width, root_wid.height),
125 font_size='22sp',
126 bold=True,
127 color=(0.67, 0, 0, 1),
128 halign='center',
129 valign='center',
130 text=_(
131 "Requested permissions are mandatory to run Cagou, if you don't "
132 "accept them, Cagou can't run properly. Please accept following "
133 "permissions, or set them in Android settings for Cagou:\n"
134 "{permissions}\n\nCagou will be closed in 20 s").format(
135 permissions='\n'.join(p.split('.')[-1] for p in permissions)))
136 root_wid.clear_widgets()
137 root_wid.add_widget(perm_warning)
138 Clock.schedule_once(lambda *args: G.host.app.stop(), 20)
139
140 def permission_cb(self, permissions, grant_results):
141 if not all(grant_results):
142 # we keep asking until they are accepted, as we can't run properly
143 # without them
144 # TODO: a message explaining why permission is needed should be printed
145 # TODO: the storage permission is mainly used to set download_dir, we should
146 # be able to run Cagou without it.
147 if not hasattr(self, 'perms_counter'):
148 self.perms_counter = 0
149 self.perms_counter += 1
150 if self.perms_counter > 5:
151 Clock.schedule_once(
152 lambda *args: self._show_perm_warning(permissions),
153 0)
154 return
155
156 perm_dict = dict(zip(permissions, grant_results))
157 log.warning(
158 f"not all mandatory permissions are granted, requesting again: "
159 f"{perm_dict}")
160 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
161 return
162
163 Clock.schedule_once(lambda *args: G.host.bridge.profileAutoconnectGet(
164 callback=self.profileAutoconnectGetCb,
165 errback=self.profileAutoconnectGetEb),
166 0)
167
112 def do_postInit(self): 168 def do_postInit(self):
113 G.host.bridge.profileAutoconnectGet( 169 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
114 callback=self.profileAutoconnectGetCb,
115 errback=self.profileAutoconnectGetEb
116 )
117 return False 170 return False
118 171
119 def onProfilePlugged(self, profile): 172 def onProfilePlugged(self, profile):
120 log.debug("ANDROID profilePlugged") 173 log.debug("ANDROID profilePlugged")
121 G.host.bridge.setParam( 174 G.host.bridge.setParam(
296 f"uri: {uri}\n" 349 f"uri: {uri}\n"
297 f"path: {path}") 350 f"path: {path}")
298 351
299 log.debug(msg) 352 log.debug(msg)
300 353
354 def check_plugin_permissions(self, plug_info, callback, errback):
355 perms = plug_info.get("android_permissons")
356 if not perms:
357 callback()
358 perms = [f"android.permission.{p}" if '.' not in p else p for p in perms]
359
360 def request_permissions_cb(permissions, granted):
361 if all(granted):
362 Clock.schedule_once(lambda *args: callback())
363 else:
364 Clock.schedule_once(lambda *args: errback())
365
366 request_permissions(perms, callback=request_permissions_cb)
367
301 def open_url(self, url, wid=None): 368 def open_url(self, url, wid=None):
302 parsed_url = urlparse(url) 369 parsed_url = urlparse(url)
303 if parsed_url.scheme == "geo": 370 if parsed_url.scheme == "geo":
304 intent = Intent(Intent.ACTION_VIEW) 371 intent = Intent(Intent.ACTION_VIEW)
305 intent.setData(Uri.parse(url)) 372 intent.setData(Uri.parse(url))