diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index 92e8db81d2..a9006d7405 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -89,6 +89,10 @@ def get_bootstrap_name(): join(curdir, 'templates'))) +DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS = 'org.kivy.android.PythonActivity' +DEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService' + + def ensure_dir(path): if not exists(path): makedirs(path) @@ -430,6 +434,7 @@ def make_package(args): service = True service_names = [] + base_service_class = args.service_class_name.split('.')[-1] for sid, spec in enumerate(args.services): spec = spec.split(':') name = spec[0] @@ -454,6 +459,7 @@ def make_package(args): foreground=foreground, sticky=sticky, service_id=sid + 1, + base_service_class=base_service_class, ) # Find the SDK directory and target API @@ -701,7 +707,7 @@ def parse_args_and_make_package(args=None): 'activity-element.html')) ap.add_argument('--android-entrypoint', dest='android_entrypoint', - default='org.kivy.android.PythonActivity', + default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS, help='Defines which java class will be used for startup, usually a subclass of PythonActivity') ap.add_argument('--android-apptheme', dest='android_apptheme', default='@android:style/Theme.NoTitleBar', @@ -800,9 +806,16 @@ def parse_args_and_make_package(args=None): ap.add_argument('--extra-manifest-xml', default='', help=('Extra xml to write directly inside the element of' 'AndroidManifest.xml')) + ap.add_argument('--extra-manifest-application-arguments', default='', + help='Extra arguments to be added to the tag of' + 'AndroidManifest.xml') ap.add_argument('--manifest-placeholders', dest='manifest_placeholders', default='[:]', help=('Inject build variables into the manifest ' 'via the manifestPlaceholders property')) + ap.add_argument('--service-class-name', dest='service_class_name', default=DEFAULT_PYTHON_SERVICE_JAVA_CLASS, + help='Use that parameter if you need to implement your own PythonServive Java class') + 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') # Put together arguments, and add those from .p4a config file: if args is None: @@ -822,6 +835,7 @@ def _read_configuration(): _read_configuration() args = ap.parse_args(args) + args.ignore_path = [] if args.name and args.name[0] == '"' and args.name[-1] == '"': diff --git a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java index 50c08f08a1..e2a7409fe8 100644 --- a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java @@ -2,10 +2,10 @@ import android.content.Intent; import android.content.Context; -import org.kivy.android.PythonService; +import {{ args.service_class_name }}; -public class Service{{ name|capitalize }} extends PythonService { +public class Service{{ name|capitalize }} extends {{ base_service_class }} { {% if sticky %} @Override public int startType() { diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml index ef65b3a7ff..340bf9f023 100644 --- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml @@ -59,8 +59,10 @@ android:icon="@drawable/icon" android:allowBackup="{{ args.allow_backup }}" {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %} + {{ args.extra_manifest_application_arguments }} android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}" - android:hardwareAccelerated="true" > + android:hardwareAccelerated="true" + > {% for l in args.android_used_libs %} {% endfor %} @@ -110,7 +112,7 @@ {% endif %} {% if service or args.launcher %} - {% endif %} {% for name in service_names %} diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index 72198a52df..6c1975a8fd 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -448,6 +448,7 @@ def __init__(self): self.copy_libs = False self.activity_class_name = u'org.kivy.android.PythonActivity' + self.service_class_name = u'org.kivy.android.PythonService' # this list should contain all Archs, it is pruned later self.archs = ( diff --git a/pythonforandroid/recipes/android/__init__.py b/pythonforandroid/recipes/android/__init__.py index c918893a22..e568ac8d9e 100644 --- a/pythonforandroid/recipes/android/__init__.py +++ b/pythonforandroid/recipes/android/__init__.py @@ -53,6 +53,7 @@ def prebuild_arch(self, arch): 'JNI_NAMESPACE': jni_ns, 'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name, 'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'), + 'SERVICE_CLASS_NAME': self.ctx.service_class_name, } # create config files for Cython, C and Python diff --git a/pythonforandroid/recipes/android/src/android/broadcast.py b/pythonforandroid/recipes/android/src/android/broadcast.py index 0910c78f60..3232d83bbf 100644 --- a/pythonforandroid/recipes/android/src/android/broadcast.py +++ b/pythonforandroid/recipes/android/src/android/broadcast.py @@ -2,7 +2,7 @@ # Broadcast receiver bridge from jnius import autoclass, PythonJavaClass, java_method -from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME +from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME class BroadcastReceiver(object): @@ -72,7 +72,7 @@ def stop(self): def context(self): from os import environ if 'PYTHON_SERVICE_ARGUMENT' in environ: - PythonService = autoclass(JAVA_NAMESPACE + '.PythonService') + PythonService = autoclass(SERVICE_CLASS_NAME) return PythonService.mService PythonActivity = autoclass(ACTIVITY_CLASS_NAME) return PythonActivity.mActivity diff --git a/pythonforandroid/recipes/android/src/android/storage.py b/pythonforandroid/recipes/android/src/android/storage.py index a7210e0ead..aa6d781f22 100644 --- a/pythonforandroid/recipes/android/src/android/storage.py +++ b/pythonforandroid/recipes/android/src/android/storage.py @@ -1,7 +1,7 @@ from jnius import autoclass, cast import os -from android.config import JAVA_NAMESPACE, ACTIVITY_CLASS_NAME +from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME Environment = autoclass('android.os.Environment') @@ -34,7 +34,7 @@ def _get_activity(): activity = PythonActivity.mActivity if activity is None: # assume we're running from the background service - PythonService = autoclass(JAVA_NAMESPACE + '.' + 'PythonService') + PythonService = autoclass(SERVICE_CLASS_NAME) activity = PythonService.mService return activity diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index d29794856d..aa242a4170 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -380,6 +380,11 @@ def __init__(self): dest='activity_class_name', default='org.kivy.android.PythonActivity', help='The full java class name of the main activity') + generic_parser.add_argument( + '--service-class-name', + dest='service_class_name', default='org.kivy.android.PythonService', + help='Full java package name of the PythonService class') + generic_parser.add_argument( '--java-build-tool', dest='java_build_tool', default='auto', @@ -613,6 +618,10 @@ def add_parser(subparsers, *args, **kwargs): args.unknown_args += ["--with-debug-symbols"] if hasattr(args, "ignore_setup_py") and args.ignore_setup_py: args.use_setup_py = False + if hasattr(args, "activity_class_name") and args.activity_class_name != 'org.kivy.android.PythonActivity': + args.unknown_args += ["--activity-class-name", args.activity_class_name] + if hasattr(args, "service_class_name") and args.service_class_name != 'org.kivy.android.PythonService': + args.unknown_args += ["--service-class-name", args.service_class_name] self.args = args @@ -709,6 +718,7 @@ def add_parser(subparsers, *args, **kwargs): self.ctx.copy_libs = args.copy_libs self.ctx.activity_class_name = args.activity_class_name + self.ctx.service_class_name = args.service_class_name # Each subparser corresponds to a method command = args.subparser_name.replace('-', '_') diff --git a/tests/test_build.py b/tests/test_build.py index 727205bd7b..27e3f40a24 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,6 +1,8 @@ import unittest from unittest import mock +import jinja2 + from pythonforandroid.build import run_pymodules_install @@ -48,3 +50,40 @@ def test_strip_if_with_debug_symbols(self): ctx.with_debug_symbols = False assert run_pymodules_install(ctx, modules, project_dir) is None assert m_CythonRecipe().strip_object_files.called is True + + +class TestTemplates(unittest.TestCase): + + def test_android_manifest_xml(self): + args = mock.Mock() + args.min_sdk_version = 12 + args.build_mode = 'debug' + args.native_services = ['abcd', ] + args.permissions = [] + args.add_activity = [] + args.android_used_libs = [] + args.meta_data = [] + args.extra_manifest_xml = '' + args.extra_manifest_application_arguments = 'android:someParameter="true" android:anotherParameter="false"' + render_args = { + "args": args, + "service": False, + "service_names": [], + "android_api": 1234, + "debug": "debug" in args.build_mode, + "native_services": args.native_services + } + environment = jinja2.Environment( + loader=jinja2.FileSystemLoader('pythonforandroid/bootstraps/sdl2/build/templates/') + ) + template = environment.get_template('AndroidManifest.tmpl.xml') + xml = template.render(**render_args) + assert xml.count('android:minSdkVersion="12"') == 1 + assert xml.count('android:anotherParameter="false"') == 1 + assert xml.count('android:someParameter="true"') == 1 + assert xml.count('') == 1 + assert xml.count('android:process=":service_') == 0 + assert xml.count('targetSdkVersion="1234"') == 1 + assert xml.count('android:debuggable="true"') == 1 + assert xml.count('') == 1 + # TODO: potentially some other checks to be added here to cover other "logic" (flags and loops) in the template diff --git a/tests/test_toolchain.py b/tests/test_toolchain.py index 887d40ecf4..d7c73c6319 100644 --- a/tests/test_toolchain.py +++ b/tests/test_toolchain.py @@ -61,6 +61,7 @@ def test_create(self): '--requirements=python3', '--dist-name=test_toolchain', '--activity-class-name=abc.myapp.android.CustomPythonActivity', + '--service-class-name=xyz.myapp.android.CustomPythonService', ] with patch_sys_argv(argv), mock.patch( 'pythonforandroid.build.get_available_apis' @@ -80,6 +81,7 @@ def test_create(self): '/tmp/android-ndk/platforms/android-21/arch-arm', True) tchain = ToolchainCL() assert tchain.ctx.activity_class_name == 'abc.myapp.android.CustomPythonActivity' + assert tchain.ctx.service_class_name == 'xyz.myapp.android.CustomPythonService' assert m_get_available_apis.call_args_list in [ [mock.call('/tmp/android-sdk')], # linux case [mock.call('/private/tmp/android-sdk')] # macos case