|
| 1 | +import threading |
1 | 2 |
|
2 | 3 | try:
|
3 |
| - from jnius import autoclass |
| 4 | + from jnius import autoclass, PythonJavaClass, java_method |
4 | 5 | except ImportError:
|
5 | 6 | # To allow importing by build/manifest-creating code without
|
6 | 7 | # pyjnius being present:
|
@@ -422,16 +423,166 @@ class Permission:
|
422 | 423 | )
|
423 | 424 |
|
424 | 425 |
|
425 |
| -def request_permissions(permissions): |
426 |
| - python_activity = autoclass('org.kivy.android.PythonActivity') |
427 |
| - python_activity.requestPermissions(permissions) |
| 426 | +PERMISSION_GRANTED = 0 |
| 427 | +PERMISSION_DENIED = -1 |
| 428 | + |
| 429 | + |
| 430 | +class _onRequestPermissionsCallback(PythonJavaClass): |
| 431 | + """Callback class for registering a Python callback from |
| 432 | + onRequestPermissionsResult in PythonActivity. |
| 433 | + """ |
| 434 | + __javainterfaces__ = ['org.kivy.android.PythonActivity$PermissionsCallback'] |
| 435 | + __javacontext__ = 'app' |
| 436 | + |
| 437 | + def __init__(self, func): |
| 438 | + self.func = func |
| 439 | + super().__init__() |
| 440 | + |
| 441 | + @java_method('(I[Ljava/lang/String;[I)V') |
| 442 | + def onRequestPermissionsResult(self, requestCode, |
| 443 | + permissions, grantResults): |
| 444 | + self.func(requestCode, permissions, grantResults) |
| 445 | + |
| 446 | + |
| 447 | +class _RequestPermissionsManager: |
| 448 | + """Internal class for requesting Android permissions. |
| 449 | +
|
| 450 | + Permissions are requested through the method 'request_permissions' which |
| 451 | + accepts a list of permissions and an optional callback. |
| 452 | +
|
| 453 | + Any callback will asynchronously receive arguments from |
| 454 | + onRequestPermissionsResult on PythonActivity after requestPermissions is |
| 455 | + called. |
| 456 | +
|
| 457 | + The callback supplied must accept two arguments: 'permissions' and |
| 458 | + 'grantResults' (as supplied to onPermissionsCallbackResult). |
| 459 | +
|
| 460 | + Note that for SDK_INT < 23, run-time permissions are not required, and so |
| 461 | + the callback will be called immediately. |
| 462 | +
|
| 463 | + The attribute '_java_callback' is initially None, but is set when the first |
| 464 | + permissions request is made. It is set to an instance of |
| 465 | + onRequestPermissionsCallback, which allows the Java callback to be |
| 466 | + propagated to the class method 'python_callback'. This is then, in turn, |
| 467 | + used to call an application callback if provided to request_permissions. |
| 468 | +
|
| 469 | + The attribute '_callback_id' is incremented with each call to |
| 470 | + request_permissions which has a callback (the value '1' is used for any |
| 471 | + call which does not pass a callback). This is passed to requestCode in |
| 472 | + the Java call, and used to identify (via the _callbacks dictionary) |
| 473 | + the matching call. |
| 474 | + """ |
| 475 | + _SDK_INT = None |
| 476 | + _java_callback = None |
| 477 | + _callbacks = {1: None} |
| 478 | + _callback_id = 1 |
| 479 | + # Lock to prevent multiple calls to request_permissions being handled |
| 480 | + # simultaneously (as incrementing _callback_id is not atomic) |
| 481 | + _lock = threading.Lock() |
| 482 | + |
| 483 | + @classmethod |
| 484 | + def register_callback(cls): |
| 485 | + """Register Java callback for requestPermissions.""" |
| 486 | + cls._java_callback = _onRequestPermissionsCallback(cls.python_callback) |
| 487 | + python_activity = autoclass('org.kivy.android.PythonActivity') |
| 488 | + python_activity.addPermissionsCallback(cls._java_callback) |
| 489 | + |
| 490 | + @classmethod |
| 491 | + def request_permissions(cls, permissions, callback=None): |
| 492 | + """Requests Android permissions from PythonActivity. |
| 493 | + If 'callback' is supplied, the request is made with a new requestCode |
| 494 | + and the callback is stored in the _callbacks dict. When a Java callback |
| 495 | + with the matching requestCode is received, callback will be called |
| 496 | + with arguments of 'permissions' and 'grant_results'. |
| 497 | + """ |
| 498 | + if not cls._SDK_INT: |
| 499 | + # Get the Android build version and store it |
| 500 | + VERSION = autoclass('android.os.Build$VERSION') |
| 501 | + cls.SDK_INT = VERSION.SDK_INT |
| 502 | + if cls.SDK_INT < 23: |
| 503 | + # No run-time permissions needed, return immediately. |
| 504 | + if callback: |
| 505 | + callback(permissions, [True for x in permissions]) |
| 506 | + return |
| 507 | + # Request permissions |
| 508 | + with cls._lock: |
| 509 | + if not cls._java_callback: |
| 510 | + cls.register_callback() |
| 511 | + python_activity = autoclass('org.kivy.android.PythonActivity') |
| 512 | + if not callback: |
| 513 | + python_activity.requestPermissions(permissions) |
| 514 | + else: |
| 515 | + cls._callback_id += 1 |
| 516 | + python_activity.requestPermissionsWithRequestCode( |
| 517 | + permissions, cls._callback_id) |
| 518 | + cls._callbacks[cls._callback_id] = callback |
428 | 519 |
|
| 520 | + @classmethod |
| 521 | + def python_callback(cls, requestCode, permissions, grantResults): |
| 522 | + """Calls the relevant callback with arguments of 'permissions' |
| 523 | + and 'grantResults'.""" |
| 524 | + # Convert from Android codes to True/False |
| 525 | + grant_results = [x == PERMISSION_GRANTED for x in grantResults] |
| 526 | + if cls._callbacks.get(requestCode): |
| 527 | + cls._callbacks[requestCode](permissions, grant_results) |
429 | 528 |
|
430 |
| -def request_permission(permission): |
431 |
| - request_permissions([permission]) |
| 529 | + |
| 530 | +# Public API methods for requesting permissions |
| 531 | + |
| 532 | +def request_permissions(permissions, callback=None): |
| 533 | + """Requests Android permissions. |
| 534 | +
|
| 535 | + Args: |
| 536 | + permissions (str): A list of permissions to requests (str) |
| 537 | + callback (callable, optional): A function to call when the request |
| 538 | + is completed (callable) |
| 539 | +
|
| 540 | + Returns: |
| 541 | + None |
| 542 | +
|
| 543 | + Notes: |
| 544 | +
|
| 545 | + Permission strings can be imported from the 'Permission' class in this |
| 546 | + module. For example: |
| 547 | +
|
| 548 | + from android import Permission |
| 549 | + permissions_list = [Permission.CAMERA, |
| 550 | + Permission.WRITE_EXTERNAL_STORAGE] |
| 551 | +
|
| 552 | + See the p4a source file 'permissions.py' for a list of valid permission |
| 553 | + strings (pythonforandroid/recipes/android/src/android/permissions.py). |
| 554 | +
|
| 555 | + Any callback supplied must accept two arguments: |
| 556 | + permissions (list of str): A list of permission strings |
| 557 | + grant_results (list of bool): A list of bools indicating whether the |
| 558 | + respective permission was granted. |
| 559 | + See Android documentation for onPermissionsCallbackResult for |
| 560 | + further information. |
| 561 | +
|
| 562 | + Note that if the request is interupted the callback may contain an empty |
| 563 | + list of permissions, without permissions being granted; the App should |
| 564 | + check that each permission requested has been granted. |
| 565 | +
|
| 566 | + Also note that when calling request_permission on SDK_INT < 23, the |
| 567 | + callback will be returned immediately as requesting permissions is not |
| 568 | + required. |
| 569 | + """ |
| 570 | + _RequestPermissionsManager.request_permissions(permissions, callback) |
| 571 | + |
| 572 | + |
| 573 | +def request_permission(permission, callback=None): |
| 574 | + request_permissions([permission], callback) |
432 | 575 |
|
433 | 576 |
|
434 | 577 | def check_permission(permission):
|
| 578 | + """Checks if an app holds the passed permission. |
| 579 | +
|
| 580 | + Args: |
| 581 | + - permission An Android permission (str) |
| 582 | +
|
| 583 | + Returns: |
| 584 | + bool: True if the app holds the permission given, False otherwise. |
| 585 | + """ |
435 | 586 | python_activity = autoclass('org.kivy.android.PythonActivity')
|
436 | 587 | result = bool(python_activity.checkCurrentPermission(
|
437 | 588 | permission + ""
|
|
0 commit comments