1
0

qml: add android permission methods in AppController

This commit is contained in:
Sander van Grieken
2025-03-29 08:17:15 +01:00
parent 4a072a45b1
commit 1ac885ea40
2 changed files with 52 additions and 6 deletions

View File

@@ -190,7 +190,7 @@ RUN cd /opt \
&& /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e .
# install python-for-android # install python-for-android
ENV P4A_CHECKOUT_COMMIT="0ab0d872e6c6b88ddc05b9c4ba6fcd3aa7921242" ENV P4A_CHECKOUT_COMMIT="32a05cdedd41f0f569e9126fdfce23069c36fd9a"
# ^ from branch electrum_20240930 (note: careful with force-pushing! see #8162) # ^ from branch electrum_20240930 (note: careful with force-pushing! see #8162)
RUN cd /opt \ RUN cd /opt \
&& git clone https://github.com/spesmilo/python-for-android \ && git clone https://github.com/spesmilo/python-for-android \

View File

@@ -5,7 +5,8 @@ import os
import sys import sys
import html import html
import threading import threading
from typing import TYPE_CHECKING, Set from functools import partial
from typing import TYPE_CHECKING, Set, List, Optional, Callable
from PyQt6.QtCore import (pyqtSlot, pyqtSignal, pyqtProperty, QObject, QT_VERSION_STR, PYQT_VERSION_STR, from PyQt6.QtCore import (pyqtSlot, pyqtSignal, pyqtProperty, QObject, QT_VERSION_STR, PYQT_VERSION_STR,
qInstallMessageHandler, QTimer, QSortFilterProxyModel) qInstallMessageHandler, QTimer, QSortFilterProxyModel)
@@ -52,7 +53,7 @@ if TYPE_CHECKING:
if 'ANDROID_DATA' in os.environ: if 'ANDROID_DATA' in os.environ:
from jnius import autoclass, cast from jnius import autoclass, cast
from android import activity from android import activity, permissions
jpythonActivity = autoclass('org.kivy.android.PythonActivity').mActivity jpythonActivity = autoclass('org.kivy.android.PythonActivity').mActivity
jHfc = autoclass('android.view.HapticFeedbackConstants') jHfc = autoclass('android.view.HapticFeedbackConstants')
@@ -88,6 +89,9 @@ class QEAppController(BaseCrashReporter, QObject):
self._intent = '' self._intent = ''
self._secureWindow = False self._secureWindow = False
# map of permissions and grant status _after_ asking user
self._permissions = {} # type: dict[str, bool]
# set up notification queue and notification_timer # set up notification queue and notification_timer
self.user_notification_queue = queue.Queue() self.user_notification_queue = queue.Queue()
self.user_notification_last_time = 0 self.user_notification_last_time = 0
@@ -124,10 +128,11 @@ class QEAppController(BaseCrashReporter, QObject):
def on_wallet_usernotify(self, wallet, message): def on_wallet_usernotify(self, wallet, message):
self.logger.debug(message) self.logger.debug(message)
self.user_notification_queue.put((wallet,message)) self.user_notification_queue.put((wallet, message))
if not self.notification_timer.isActive(): if not self.notification_timer.isActive():
self.logger.debug('starting app notification timer') self.logger.debug('starting app notification timer')
self.notification_timer.start() self.notification_timer.start()
self.on_notification_timer()
def on_notification_timer(self): def on_notification_timer(self):
if self.user_notification_queue.qsize() == 0: if self.user_notification_queue.qsize() == 0:
@@ -140,6 +145,13 @@ class QEAppController(BaseCrashReporter, QObject):
return return
self.user_notification_last_time = now self.user_notification_last_time = now
self.logger.info("Notifying GUI about new user notifications") self.logger.info("Notifying GUI about new user notifications")
# request permission and defer notify until after permission request callback
# note: permission request is only shown to user once, so it is safe to request
# multiple times
if self.isAndroid() and not self.hasPermission(permissions.Permission.POST_NOTIFICATIONS) \
and self._permissions.get(permissions.Permission.POST_NOTIFICATIONS) is None:
self.request_permission(permissions.Permission.POST_NOTIFICATIONS)
return
try: try:
wallet, message = self.user_notification_queue.get_nowait() wallet, message = self.user_notification_queue.get_nowait()
self.userNotify.emit(str(wallet), message) self.userNotify.emit(str(wallet), message)
@@ -148,8 +160,6 @@ class QEAppController(BaseCrashReporter, QObject):
def doNotify(self, wallet_name, message): def doNotify(self, wallet_name, message):
self.logger.debug(f'sending push notification to OS: {message=!r}') self.logger.debug(f'sending push notification to OS: {message=!r}')
# FIXME: this does not work on Android 13+. We would need to declare (in manifest)
# and also request-at-runtime android.permission.POST_NOTIFICATIONS.
try: try:
# TODO: lazy load not in UI thread please # TODO: lazy load not in UI thread please
global notification global notification
@@ -173,6 +183,42 @@ class QEAppController(BaseCrashReporter, QObject):
except Exception as e: except Exception as e:
self.logger.error(f'unable to bind intent: {repr(e)}') self.logger.error(f'unable to bind intent: {repr(e)}')
@pyqtSlot(str, result=bool)
def hasPermission(self, permissionFqcn: str) -> bool:
if not self.isAndroid():
return True
result = permissions.check_permission(permissionFqcn)
return result
def request_permission(self, permissionFqcn: str, permission_result_cb: Optional[Callable] = None):
if not self.isAndroid():
return True
self.logger.debug(f'requesting {permissionFqcn=}')
permissions.request_permission(
permissionFqcn,
callback=partial(self.on_request_permissions_result, permissionFqcn, permission_result_cb)
)
def on_request_permissions_result(
self,
permission: str,
permission_result_cb: Optional[Callable[[bool], None]],
permissions: List[str],
grant_results: List[bool]
):
self.logger.debug(f'on_request_permissions_result, len={len(permissions)}, p={repr(permissions)}, g={repr(grant_results)}')
grant_result = None
try:
grant_result = grant_results[permissions.index(permission)]
except ValueError:
pass
if grant_result is not None:
self._permissions[permission] = grant_result
if permission_result_cb:
permission_result_cb(grant_result)
def on_new_intent(self, intent): def on_new_intent(self, intent):
if not self._app_started: if not self._app_started:
self._intent = intent self._intent = intent