diff --git a/CHANGELOG.md b/CHANGELOG.md index dda944b49b..1ba38ca865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [v2023.01.28](https://github.com/kivy/python-for-android/tree/v2023.01.28) (2023-01-28) + +[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.12.20...v2023.01.28) + +**Closed issues:** + +- Python + [\#2737](https://github.com/kivy/python-for-android/issues/2737) +- AndroidX Issue [\#2736](https://github.com/kivy/python-for-android/issues/2736) +- Kivy build failed [\#2735](https://github.com/kivy/python-for-android/issues/2735) +- Can't build apk using READ\_EXTERNAL\_STORAGE, WRITE\_EXTERNAL\_STORAGE in buildozer.spec [\#2732](https://github.com/kivy/python-for-android/issues/2732) +- BUILD FAILURE: No main.py\(o\) found in your app directory. [\#2731](https://github.com/kivy/python-for-android/issues/2731) +- Your app currently targets API level 30 and must target at least API level 31 to ensure that it is built on the latest APIs optimised for security and performance. Change your app's target API level to at least 31 [\#2729](https://github.com/kivy/python-for-android/issues/2729) +- Your app currently targets API level 30 and must target at least API level 31 to ensure that it is built on the latest APIs optimised for security and performance. Change your app's target API level to at least 31 [\#2727](https://github.com/kivy/python-for-android/issues/2727) +- `sh.CommandNotFound: ./download.sh` [\#2726](https://github.com/kivy/python-for-android/issues/2726) +- Setting `android:screenOrientation` via `--orientation` has no effect when targeting API 31 [\#2724](https://github.com/kivy/python-for-android/issues/2724) +- \[Question\]: How to set 'compileSdkVersion' to 31 or higher. [\#2722](https://github.com/kivy/python-for-android/issues/2722) +- Bug in the keyboard event listener [\#2423](https://github.com/kivy/python-for-android/issues/2423) + +**Merged pull requests:** + +- Implements `--manifest-orientation` and changes how `--orientation` works so we can now pass the setting to the SDL orientation hint [\#2739](https://github.com/kivy/python-for-android/pull/2739) ([misl6](https://github.com/misl6)) +- Update \_\_init\_\_.py from `scrypt` recipe [\#2738](https://github.com/kivy/python-for-android/pull/2738) ([FilipeMarch](https://github.com/FilipeMarch)) +- Apply a patch from SDL upstream that fixes orientation settings [\#2730](https://github.com/kivy/python-for-android/pull/2730) ([misl6](https://github.com/misl6)) +- Support permission properties \(`maxSdkVersion` and `usesPermissionFlags`\) + remove `WRITE_EXTERNAL_STORAGE` permission, which has been previously declared by default [\#2725](https://github.com/kivy/python-for-android/pull/2725) ([misl6](https://github.com/misl6)) +- Merge master in develop [\#2721](https://github.com/kivy/python-for-android/pull/2721) ([misl6](https://github.com/misl6)) + ## [v2022.12.20](https://github.com/kivy/python-for-android/tree/v2022.12.20) (2022-12-20) [Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.09.04...v2022.12.20) diff --git a/Makefile b/Makefile index 97f502219e..ea5adadcd8 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,8 @@ testapps-with-numpy: virtualenv . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ python setup.py apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ --requirements libffi,sdl2,pyjnius,kivy,python3,openssl,requests,urllib3,chardet,idna,sqlite3,setuptools,numpy \ - --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 + --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 \ + --permission "(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)" --permission "(name=android.permission.INTERNET)" testapps-with-scipy: virtualenv . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ @@ -53,7 +54,8 @@ testapps-with-numpy-aab: virtualenv . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ python setup.py aab --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ --requirements libffi,sdl2,pyjnius,kivy,python3,openssl,requests,urllib3,chardet,idna,sqlite3,setuptools,numpy \ - --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 --release + --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 --release \ + --permission "(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)" --permission "(name=android.permission.INTERNET)" testapps-service_library-aar: virtualenv . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ diff --git a/doc/source/buildoptions.rst b/doc/source/buildoptions.rst index 05ce4ef699..98e07712d5 100644 --- a/doc/source/buildoptions.rst +++ b/doc/source/buildoptions.rst @@ -57,16 +57,36 @@ options (this list may not be exhaustive): - ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``. - ``--name``: The app name. - ``--version``: The version number. -- ``--orientation``: Usually one of ``portait``, ``landscape``, - ``sensor`` to automatically rotate according to the device - orientation, or ``user`` to do the same but obeying the user's - settings. The full list of valid options is given under - ``android:screenOrientation`` in the `Android documentation - `__. +- ``--orientation``: The orientations that the app will display in. + (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``). + Since Android ignores ``android:screenOrientation`` when in multi-window mode + (Which is the default on Android 12+), this option will also set the window orientation hints + for the SDL bootstrap. If multiple orientations are given, +``android:screenOrientation`` will be set to ``unspecified``. +- ``--manifest-orientation``: The orientation that will be set for the ``android:screenOrientation`` + attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value + will be synthesized from the ``--orientation`` option. + The full list of valid options is given under ``android:screenOrientation`` + in the `Android documentation `__. - ``--icon``: A path to the png file to use as the application icon. -- ``--permission``: A permission name for the app, - e.g. ``--permission VIBRATE``. For multiple permissions, add - multiple ``--permission`` arguments. +- ``--permission``: A permission that needs to be declared into the App ``AndroidManifest.xml``. + For multiple permissions, add multiple ``--permission`` arguments. + + .. Note :: + ``--permission`` accepts the following syntaxes: + ``--permission (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)`` + or ``--permission android.permission.WRITE_EXTERNAL_STORAGE``. + + The first syntax is used to set additional properties to the permission + (``android:maxSdkVersion`` and ``android:usesPermissionFlags`` are the only ones supported for now). + + The second one can be used when there's no need to add any additional properties. + + .. Warning :: + The syntax ``--permission VIBRATE`` (only the permission name, without the prefix), + is also supported for backward compatibility, but it will be removed in the future. + + - ``--meta-data``: Custom key=value pairs to add in the application metadata. - ``--presplash``: A path to the image file to use as a screen while the application is loading. @@ -121,12 +141,16 @@ ready. - ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``. - ``--name``: The app name. - ``--version``: The version number. -- ``--orientation``: Usually one of ``portait``, ``landscape``, - ``sensor`` to automatically rotate according to the device - orientation, or ``user`` to do the same but obeying the user's - settings. The full list of valid options is given under - ``android:screenOrientation`` in the `Android documentation - `__. +- ``--orientation``: The orientations that the app will display in. + (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``). + Since Android ignores ``android:screenOrientation`` when in multi-window mode + (Which is the default on Android 12+), this setting is not guaranteed to work, and + you should consider to implement a custom orientation change handler in your app. +- ``--manifest-orientation``: The orientation that will be set in the ``android:screenOrientation`` + attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value + will be synthesized from the ``--orientation`` option. + The full list of valid options is given under ``android:screenOrientation`` + in the `Android documentation `__. - ``--icon``: A path to the png file to use as the application icon. - ``--permission``: A permission name for the app, e.g. ``--permission VIBRATE``. For multiple permissions, add diff --git a/pythonforandroid/__init__.py b/pythonforandroid/__init__.py index 8364e6416d..71bdd8017e 100644 --- a/pythonforandroid/__init__.py +++ b/pythonforandroid/__init__.py @@ -1 +1 @@ -__version__ = '2022.12.20' +__version__ = '2023.01.28' diff --git a/pythonforandroid/bdistapk.py b/pythonforandroid/bdistapk.py index bcf77cd60d..575e0e17e5 100644 --- a/pythonforandroid/bdistapk.py +++ b/pythonforandroid/bdistapk.py @@ -43,6 +43,9 @@ def finalize_options(self): if option == 'permissions': for perm in value: sys.argv.append('--permission={}'.format(perm)) + elif option == 'orientation': + for orient in value: + sys.argv.append('--orientation={}'.format(orient)) elif value in (None, 'None'): sys.argv.append('--{}'.format(option)) else: diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index c49d18fc4d..01f5c881b5 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -53,10 +53,6 @@ def get_bootstrap_name(): curdir = dirname(__file__) -PYTHON = get_hostpython() -if PYTHON is not None and not exists(PYTHON): - PYTHON = None - BLACKLIST_PATTERNS = [ # code versionning '^*.hg/*', @@ -75,9 +71,19 @@ def get_bootstrap_name(): ] WHITELIST_PATTERNS = [] -if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'): - WHITELIST_PATTERNS.append('pyconfig.h') +if os.environ.get("P4A_BUILD_IS_RUNNING_UNITTESTS", "0") != "1": + PYTHON = get_hostpython() + _bootstrap_name = get_bootstrap_name() +else: + PYTHON = "python3" + _bootstrap_name = "sdl2" + +if PYTHON is not None and not exists(PYTHON): + PYTHON = None + +if _bootstrap_name in ('sdl2', 'webview', 'service_only'): + WHITELIST_PATTERNS.append('pyconfig.h') environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) @@ -243,8 +249,8 @@ def make_package(args): with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f: if hasattr(args, "window"): f.write("P4A_IS_WINDOWED=" + str(args.window) + "\n") - if hasattr(args, "orientation"): - f.write("P4A_ORIENTATION=" + str(args.orientation) + "\n") + if hasattr(args, "sdl_orientation_hint"): + f.write("KIVY_ORIENTATION=" + str(args.sdl_orientation_hint) + "\n") f.write("P4A_NUMERIC_VERSION=" + str(args.numeric_version) + "\n") f.write("P4A_MINSDK=" + str(args.min_sdk_version) + "\n") @@ -646,20 +652,92 @@ def make_package(args): subprocess.check_output(patch_command) -def parse_args_and_make_package(args=None): - global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON +def parse_permissions(args_permissions): + if args_permissions and isinstance(args_permissions[0], list): + args_permissions = [p for perm in args_permissions for p in perm] + + def _is_advanced_permission(permission): + return permission.startswith("(") and permission.endswith(")") + + def _decode_advanced_permission(permission): + SUPPORTED_PERMISSION_PROPERTIES = ["name", "maxSdkVersion", "usesPermissionFlags"] + _permission_args = permission[1:-1].split(";") + _permission_args = (arg.split("=") for arg in _permission_args) + advanced_permission = dict(_permission_args) + + if "name" not in advanced_permission: + raise ValueError("Advanced permission must have a name property") + + for key in advanced_permission.keys(): + if key not in SUPPORTED_PERMISSION_PROPERTIES: + raise ValueError( + f"Property '{key}' is not supported. " + "Advanced permission only supports: " + f"{', '.join(SUPPORTED_PERMISSION_PROPERTIES)} properties" + ) + + return advanced_permission + + _permissions = [] + for permission in args_permissions: + if _is_advanced_permission(permission): + _permissions.append(_decode_advanced_permission(permission)) + else: + if "." in permission: + _permissions.append(dict(name=permission)) + else: + _permissions.append(dict(name=f"android.permission.{permission}")) + return _permissions + + +def get_sdl_orientation_hint(orientations): + SDL_ORIENTATION_MAP = { + "landscape": "LandscapeLeft", + "portrait": "Portrait", + "portrait-reverse": "PortraitUpsideDown", + "landscape-reverse": "LandscapeRight", + } + return " ".join( + [SDL_ORIENTATION_MAP[x] for x in orientations if x in SDL_ORIENTATION_MAP] + ) + +def get_manifest_orientation(orientations, manifest_orientation=None): + # If the user has specifically set an orientation to use in the manifest, + # use that. + if manifest_orientation is not None: + return manifest_orientation + + # If multiple or no orientations are specified, use unspecified in the manifest, + # as we can only specify one orientation in the manifest. + if len(orientations) != 1: + return "unspecified" + + # Convert the orientation to a value that can be used in the manifest. + # If the specified orientation is not supported, use unspecified. + MANIFEST_ORIENTATION_MAP = { + "landscape": "landscape", + "portrait": "portrait", + "portrait-reverse": "reversePortrait", + "landscape-reverse": "reverseLandscape", + } + return MANIFEST_ORIENTATION_MAP.get(orientations[0], "unspecified") + + +def get_dist_ndk_min_api_level(): # Get the default minsdk, equal to the NDK API that this dist is built against try: with open('dist_info.json', 'r') as fileh: info = json.load(fileh) - default_min_api = int(info['ndk_api']) - ndk_api = default_min_api + ndk_api = int(info['ndk_api']) except (OSError, KeyError, ValueError, TypeError): print('WARNING: Failed to read ndk_api from dist info, defaulting to 12') - default_min_api = 12 # The old default before ndk_api was introduced - ndk_api = 12 + ndk_api = 12 # The old default before ndk_api was introduced + return ndk_api + +def create_argument_parser(): + ndk_api = get_dist_ndk_min_api_level() import argparse ap = argparse.ArgumentParser(description='''\ Package a Python application for Android (using @@ -742,19 +820,21 @@ def parse_args_and_make_package(args=None): ap.add_argument('--window', dest='window', action='store_true', default=False, help='Indicate if the application will be windowed') + ap.add_argument('--manifest-orientation', dest='manifest_orientation', + help=('The orientation that will be set in the ' + 'android:screenOrientation attribute of the activity ' + 'in the AndroidManifest.xml file. If not set, ' + 'the value will be synthesized from the --orientation option.')) ap.add_argument('--orientation', dest='orientation', - default='portrait', - help=('The orientation that the game will ' - 'display in. ' - 'Usually one of "landscape", "portrait", ' - '"sensor", or "user" (the same as "sensor" ' - 'but obeying the ' - 'user\'s Android rotation setting). ' - 'The full list of options is given under ' - 'android_screenOrientation at ' - 'https://developer.android.com/guide/' - 'topics/manifest/' - 'activity-element.html')) + action="append", default=[], + choices=['portrait', 'landscape', 'landscape-reverse', 'portrait-reverse'], + help=('The orientations that the app will display in. ' + 'Since Android ignores android:screenOrientation ' + 'when in multi-window mode (Which is the default on Android 12+), ' + 'this option will also set the window orientation hints ' + 'for apps using the (default) SDL bootstrap.' + 'If multiple orientations are given, android:screenOrientation ' + 'will be set to "unspecified"')) ap.add_argument('--enable-androidx', dest='enable_androidx', action='store_true', @@ -809,9 +889,9 @@ def parse_args_and_make_package(args=None): ap.add_argument('--sdk', dest='sdk_version', default=-1, type=int, help=('Deprecated argument, does nothing')) ap.add_argument('--minsdk', dest='min_sdk_version', - default=default_min_api, type=int, + default=ndk_api, type=int, help=('Minimum Android SDK version that the app supports. ' - 'Defaults to {}.'.format(default_min_api))) + 'Defaults to {}.'.format(ndk_api))) ap.add_argument('--allow-minsdk-ndkapi-mismatch', default=False, action='store_true', help=('Allow the --minsdk argument to be different from ' @@ -874,6 +954,15 @@ def parse_args_and_make_package(args=None): ap.add_argument('--activity-class-name', dest='activity_class_name', default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS, help='The full java class name of the main activity') + return ap + + +def parse_args_and_make_package(args=None): + global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON + + ndk_api = get_dist_ndk_min_api_level() + ap = create_argument_parser() + # Put together arguments, and add those from .p4a config file: if args is None: args = sys.argv[1:] @@ -918,8 +1007,14 @@ def _read_configuration(): 'deprecated and does nothing.') args.sdk_version = -1 # ensure it is not used - if args.permissions and isinstance(args.permissions[0], list): - args.permissions = [p for perm in args.permissions for p in perm] + args.permissions = parse_permissions(args.permissions) + + args.manifest_orientation = get_manifest_orientation( + args.orientation, args.manifest_orientation + ) + + if get_bootstrap_name() == "sdl2": + args.sdl_orientation_hint = get_sdl_orientation_hint(args.orientation) if args.res_xmls and isinstance(args.res_xmls[0], list): args.res_xmls = [x for res in args.res_xmls for x in res] @@ -959,4 +1054,6 @@ def _read_configuration(): if __name__ == "__main__": + if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'): + WHITELIST_PATTERNS.append('pyconfig.h') parse_args_and_make_package() diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml index b5ddde3874..f33e9c81e0 100644 --- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml @@ -24,14 +24,9 @@ - - + {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} @@ -74,7 +69,7 @@ {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} diff --git a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml index f77533b1e6..1b83cd83e3 100644 --- a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml @@ -21,11 +21,7 @@ {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} @@ -65,7 +61,7 @@ ') == 1 + assert xml.count('') == 1 + assert xml.count('') == 1 + assert xml.count('') == 1 # TODO: potentially some other checks to be added here to cover other "logic" (flags and loops) in the template