qml: add android permission methods in AppController
This commit is contained in:
@@ -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 \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user