From de1014de91f7572a02577c5bbb7c63df8f8871e8 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:19:30 +0200 Subject: [PATCH 01/15] copy bootstrap webview --- .../bootstraps/service_only/__init__.py | 119 +++++ .../service_only/build/AndroidManifest.xml | 45 ++ .../service_only/build/ant.properties | 18 + .../service_only/build/blacklist.txt | 90 ++++ .../service_only/build/build.properties | 21 + .../bootstraps/service_only/build/build.py | 494 ++++++++++++++++++ .../bootstraps/service_only/build/build.xml | 93 ++++ .../service_only/build/jni/Android.mk | 1 + .../service_only/build/jni/Application.mk | 7 + .../service_only/build/jni/src/Android.mk | 24 + .../build/jni/src/Android_static.mk | 12 + .../service_only/build/jni/src/pyjniusjni.c | 103 ++++ .../service_only/build/jni/src/start.c | 355 +++++++++++++ .../service_only/build/proguard-project.txt | 20 + .../service_only/build/res/drawable/.gitkeep | 0 .../service_only/build/res/drawable/icon.png | Bin 0 -> 16525 bytes .../service_only/build/res/layout/main.xml | 13 + .../service_only/build/res/values/strings.xml | 5 + .../build/src/org/kamranzafar/jtar/Octal.java | 141 +++++ .../org/kamranzafar/jtar/TarConstants.java | 28 + .../src/org/kamranzafar/jtar/TarEntry.java | 284 ++++++++++ .../src/org/kamranzafar/jtar/TarHeader.java | 243 +++++++++ .../org/kamranzafar/jtar/TarInputStream.java | 249 +++++++++ .../org/kamranzafar/jtar/TarOutputStream.java | 163 ++++++ .../src/org/kamranzafar/jtar/TarUtils.java | 96 ++++ .../android/GenericBroadcastReceiver.java | 19 + .../GenericBroadcastReceiverCallback.java | 8 + .../src/org/kivy/android/PythonActivity.java | 393 ++++++++++++++ .../src/org/kivy/android/PythonService.java | 129 +++++ .../src/org/kivy/android/PythonUtil.java | 56 ++ .../kivy/android/concurrency/PythonEvent.java | 45 ++ .../kivy/android/concurrency/PythonLock.java | 19 + .../src/org/renpy/android/AssetExtract.java | 115 ++++ .../build/src/org/renpy/android/Hardware.java | 287 ++++++++++ .../src/org/renpy/android/PythonActivity.java | 12 + .../src/org/renpy/android/PythonService.java | 12 + .../org/renpy/android/ResourceManager.java | 54 ++ .../build/templates/AndroidManifest.tmpl.xml | 95 ++++ .../build/templates/Service.tmpl.java | 56 ++ .../build/templates/WebViewLoader.tmpl.java | 59 +++ .../build/templates/build.tmpl.xml | 95 ++++ .../build/templates/custom_rules.tmpl.xml | 14 + .../build/templates/kivy-icon.png | Bin 0 -> 16525 bytes .../build/templates/kivy-presplash.jpg | Bin 0 -> 18251 bytes .../build/templates/strings.tmpl.xml | 5 + .../build/templates/test/build.tmpl.xml | 93 ++++ .../build/templates/test/build.xml.tmpl | 93 ++++ .../build/webview_includes/_load.html | 60 +++ .../service_only/build/whitelist.txt | 1 + 49 files changed, 4344 insertions(+) create mode 100644 pythonforandroid/bootstraps/service_only/__init__.py create mode 100644 pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/ant.properties create mode 100644 pythonforandroid/bootstraps/service_only/build/blacklist.txt create mode 100644 pythonforandroid/bootstraps/service_only/build/build.properties create mode 100755 pythonforandroid/bootstraps/service_only/build/build.py create mode 100644 pythonforandroid/bootstraps/service_only/build/build.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/Android.mk create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/Application.mk create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c create mode 100644 pythonforandroid/bootstraps/service_only/build/jni/src/start.c create mode 100644 pythonforandroid/bootstraps/service_only/build/proguard-project.txt create mode 100644 pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep create mode 100644 pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png create mode 100644 pythonforandroid/bootstraps/service_only/build/res/layout/main.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/res/values/strings.xml create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java create mode 100755 pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl create mode 100644 pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html create mode 100644 pythonforandroid/bootstraps/service_only/build/whitelist.txt diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py new file mode 100644 index 0000000000..315092d7dd --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/__init__.py @@ -0,0 +1,119 @@ +from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main +from os.path import join, exists, curdir, abspath +from os import walk +import glob +import sh + +class WebViewBootstrap(Bootstrap): + name = 'webview' + + recipe_depends = ['webviewjni', ('python2', 'python3crystax')] + + def run_distribute(self): + info_main('# Creating Android project from build and {} bootstrap'.format( + self.name)) + + shprint(sh.rm, '-rf', self.dist_dir) + shprint(sh.cp, '-r', self.build_dir, self.dist_dir) + with current_directory(self.dist_dir): + with open('local.properties', 'w') as fileh: + fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) + + arch = self.ctx.archs[0] + if len(self.ctx.archs) > 1: + raise ValueError('built for more than one arch, but bootstrap cannot handle that yet') + info('Bootstrap running with arch {}'.format(arch)) + + with current_directory(self.dist_dir): + info('Copying python distribution') + + if not exists('private') and not self.ctx.python_recipe.from_crystax: + shprint(sh.mkdir, 'private') + if not exists('crystax_python') and self.ctx.python_recipe.from_crystax: + shprint(sh.mkdir, 'crystax_python') + shprint(sh.mkdir, 'crystax_python/crystax_python') + if not exists('assets'): + shprint(sh.mkdir, 'assets') + + hostpython = sh.Command(self.ctx.hostpython) + if not self.ctx.python_recipe.from_crystax: + try: + shprint(hostpython, '-OO', '-m', 'compileall', + self.ctx.get_python_install_dir(), + _tail=10, _filterout="^Listing") + except sh.ErrorReturnCode: + pass + if not exists('python-install'): + shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install') + + self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) + self.distribute_aars(arch) + self.distribute_javaclasses(self.ctx.javaclass_dir) + + if not self.ctx.python_recipe.from_crystax: + info('Filling private directory') + if not exists(join('private', 'lib')): + info('private/lib does not exist, making') + shprint(sh.cp, '-a', join('python-install', 'lib'), 'private') + shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7')) + + # AND: Copylibs stuff should go here + if exists(join('libs', arch.arch, 'libpymodules.so')): + shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/') + shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/')) + + info('Removing some unwanted files') + shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so')) + shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig')) + + libdir = join(self.dist_dir, 'private', 'lib', 'python2.7') + site_packages_dir = join(libdir, 'site-packages') + with current_directory(libdir): + # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.'))) + removes = [] + for dirname, something, filens in walk('.'): + for filename in filens: + for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'): + if filename.endswith(suffix): + removes.append(filename) + shprint(sh.rm, '-f', *removes) + + info('Deleting some other stuff not used on android') + # To quote the original distribute.sh, 'well...' + # shprint(sh.rm, '-rf', 'ctypes') + shprint(sh.rm, '-rf', 'lib2to3') + shprint(sh.rm, '-rf', 'idlelib') + for filename in glob.glob('config/libpython*.a'): + shprint(sh.rm, '-f', filename) + shprint(sh.rm, '-rf', 'config/python.o') + # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so') + # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so') + + else: # Python *is* loaded from crystax + ndk_dir = self.ctx.ndk_dir + py_recipe = self.ctx.python_recipe + python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version, + 'libs', arch.arch) + + shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python') + shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python') + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages') + + info('Renaming .so files to reflect cross-compile') + site_packages_dir = 'crystax_python/crystax_python/site-packages' + filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode( + 'utf-8').split('\n')[:-1] + for filen in filens: + parts = filen.split('.') + if len(parts) <= 2: + continue + shprint(sh.mv, filen, filen.split('.')[0] + '.so') + site_packages_dir = join(abspath(curdir), + site_packages_dir) + + + self.strip_libraries(arch) + self.fry_eggs(site_packages_dir) + super(WebViewBootstrap, self).run_distribute() + +bootstrap = WebViewBootstrap() diff --git a/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml b/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml new file mode 100644 index 0000000000..a3dfc7b224 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/ant.properties b/pythonforandroid/bootstraps/service_only/build/ant.properties new file mode 100644 index 0000000000..f74e644b8a --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/ant.properties @@ -0,0 +1,18 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +source.absolute.dir = tmp-src diff --git a/pythonforandroid/bootstraps/service_only/build/blacklist.txt b/pythonforandroid/bootstraps/service_only/build/blacklist.txt new file mode 100644 index 0000000000..d220d2a2ae --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/blacklist.txt @@ -0,0 +1,90 @@ +# prevent user to include invalid extensions +*.apk +*.pxd + +# eggs +*.egg-info + +# unit test +unittest/* + +# python config +config/makesetup + +# unused kivy files (platform specific) +kivy/input/providers/wm_* +kivy/input/providers/mactouch* +kivy/input/providers/probesysfs* +kivy/input/providers/mtdev* +kivy/input/providers/hidinput* +kivy/core/camera/camera_videocapture* +kivy/core/spelling/*osx* +kivy/core/video/video_pyglet* +kivy/tools +kivy/tests/* +kivy/*/*.h +kivy/*/*.pxi + +# unused encodings +lib-dynload/*codec* +encodings/cp*.pyo +encodings/tis* +encodings/shift* +encodings/bz2* +encodings/iso* +encodings/undefined* +encodings/johab* +encodings/p* +encodings/m* +encodings/euc* +encodings/k* +encodings/unicode_internal* +encodings/quo* +encodings/gb* +encodings/big5* +encodings/hp* +encodings/hz* + +# unused python modules +bsddb/* +wsgiref/* +hotshot/* +pydoc_data/* +tty.pyo +anydbm.pyo +nturl2path.pyo +LICENCE.txt +macurl2path.pyo +dummy_threading.pyo +audiodev.pyo +antigravity.pyo +dumbdbm.pyo +sndhdr.pyo +__phello__.foo.pyo +sunaudio.pyo +os2emxpath.pyo +multiprocessing/dummy* + +# unused binaries python modules +lib-dynload/termios.so +lib-dynload/_lsprof.so +lib-dynload/*audioop.so +lib-dynload/mmap.so +lib-dynload/_hotshot.so +lib-dynload/_heapq.so +lib-dynload/_json.so +lib-dynload/grp.so +lib-dynload/resource.so +lib-dynload/pyexpat.so +lib-dynload/_ctypes_test.so +lib-dynload/_testcapi.so + +# odd files +plat-linux3/regen + +#>sqlite3 +# conditionnal include depending if some recipes are included or not. +sqlite3/* +lib-dynload/_sqlite3.so +#[0-9\.]*<', + '"private_version">{}<'.format( + str(time.time())), lines)) + + +def parse_args(args=None): + global BLACKLIST_PATTERNS, WHITELIST_PATTERNS + default_android_api = 12 + import argparse + ap = argparse.ArgumentParser(description='''\ +Package a Python application for Android. + +For this to work, Java and Ant need to be in your path, as does the +tools directory of the Android SDK. +''') + + ap.add_argument('--private', dest='private', + help='the dir of user files', + required=True) + ap.add_argument('--package', dest='package', + help=('The name of the java package the project will be' + ' packaged under.'), + required=True) + ap.add_argument('--name', dest='name', + help=('The human-readable name of the project.'), + required=True) + ap.add_argument('--numeric-version', dest='numeric_version', + help=('The numeric version number of the project. If not ' + 'given, this is automatically computed from the ' + 'version.')) + ap.add_argument('--version', dest='version', + help=('The version number of the project. This should ' + 'consist of numbers and dots, and should have the ' + 'same number of groups of numbers as previous ' + 'versions.'), + required=True) + ap.add_argument('--orientation', dest='orientation', default='portrait', + help=('The orientation that the game will display in. ' + 'Usually one of "landscape", "portrait" or ' + '"sensor"')) + ap.add_argument('--icon', dest='icon', + help='A png file to use as the icon for the application.') + ap.add_argument('--permission', dest='permissions', action='append', + help='The permissions to give this app.') + ap.add_argument('--meta-data', dest='meta_data', action='append', + help='Custom key=value to add in application metadata') + ap.add_argument('--presplash', dest='presplash', + help=('A jpeg file to use as a screen while the ' + 'application is loading.')) + ap.add_argument('--wakelock', dest='wakelock', action='store_true', + help=('Indicate if the application needs the device ' + 'to stay on')) + ap.add_argument('--window', dest='window', action='store_false', + help='Indicate if the application will be windowed') + ap.add_argument('--blacklist', dest='blacklist', + default=join(curdir, 'blacklist.txt'), + help=('Use a blacklist file to match unwanted file in ' + 'the final APK')) + ap.add_argument('--whitelist', dest='whitelist', + default=join(curdir, 'whitelist.txt'), + help=('Use a whitelist file to prevent blacklisting of ' + 'file in the final APK')) + ap.add_argument('--add-jar', dest='add_jar', action='append', + help=('Add a Java .jar to the libs, so you can access its ' + 'classes with pyjnius. You can specify this ' + 'argument more than once to include multiple jars')) + ap.add_argument('--sdk', dest='sdk_version', default=-1, + type=int, help=('Android SDK version to use. Default to ' + 'the value of minsdk')) + ap.add_argument('--minsdk', dest='min_sdk_version', + default=default_android_api, type=int, + help=('Minimum Android SDK version to use. Default to ' + 'the value of ANDROIDAPI, or {} if not set' + .format(default_android_api))) + ap.add_argument('--intent-filters', dest='intent_filters', + help=('Add intent-filters xml rules to the ' + 'AndroidManifest.xml file. The argument is a ' + 'filename containing xml. The filename should be ' + 'located relative to the python-for-android ' + 'directory')) + ap.add_argument('--with-billing', dest='billing_pubkey', + help='If set, the billing service will be added (not implemented)') + ap.add_argument('--service', dest='services', action='append', + help='Declare a new service entrypoint: ' + 'NAME:PATH_TO_PY[:foreground]') + ap.add_argument('--add-source', dest='extra_source_dirs', action='append', + help='Include additional source dirs in Java build') + ap.add_argument('--port', help='The port on localhost that the WebView will access', + default='5000') + + if args is None: + args = sys.argv[1:] + args = ap.parse_args(args) + args.ignore_path = [] + + if args.billing_pubkey: + print('Billing not yet supported in sdl2 bootstrap!') + exit(1) + + if args.sdk_version == -1: + args.sdk_version = args.min_sdk_version + + if args.permissions is None: + args.permissions = [] + + if args.meta_data is None: + args.meta_data = [] + + if args.services is None: + args.services = [] + + if args.blacklist: + with open(args.blacklist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + BLACKLIST_PATTERNS += patterns + + if args.whitelist: + with open(args.whitelist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + WHITELIST_PATTERNS += patterns + + make_package(args) + + return args + + +if __name__ == "__main__": + + parse_args() diff --git a/pythonforandroid/bootstraps/service_only/build/build.xml b/pythonforandroid/bootstraps/service_only/build/build.xml new file mode 100644 index 0000000000..9f19a077b1 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/jni/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/Android.mk new file mode 100644 index 0000000000..5053e7d643 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/pythonforandroid/bootstraps/service_only/build/jni/Application.mk b/pythonforandroid/bootstraps/service_only/build/jni/Application.mk new file mode 100644 index 0000000000..e79e378f94 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/Application.mk @@ -0,0 +1,7 @@ + +# Uncomment this if you're using STL in your project +# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information +# APP_STL := stlport_static + +# APP_ABI := armeabi armeabi-v7a x86 +APP_ABI := $(ARCH) diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk new file mode 100644 index 0000000000..b431059f12 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk @@ -0,0 +1,24 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +# LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include + +# Add your application source files here... +LOCAL_SRC_FILES := start.c pyjniusjni.c + +LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS) + +LOCAL_SHARED_LIBRARIES := python_shared + +LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS) + +LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS) + +include $(BUILD_SHARED_LIBRARY) + +ifdef CRYSTAX_PYTHON_VERSION + $(call import-module,python/$(CRYSTAX_PYTHON_VERSION)) +endif diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk b/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk new file mode 100644 index 0000000000..faed669c0e --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk @@ -0,0 +1,12 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +LOCAL_SRC_FILES := YourSourceHere.c + +LOCAL_STATIC_LIBRARIES := SDL2_static + +include $(BUILD_SHARED_LIBRARY) +$(call import-module,SDL)LOCAL_PATH := $(call my-dir) diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c b/pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c new file mode 100644 index 0000000000..d67972a4db --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c @@ -0,0 +1,103 @@ + +#include +#include + +#define LOGI(...) do {} while (0) +#define LOGE(...) do {} while (0) + +#include "android/log.h" + +/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */ + +/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */ +/* #define LOGP(x) LOG("python", (x)) */ +#define LOG_TAG "Python_android" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + + +/* Function headers */ +JNIEnv* Android_JNI_GetEnv(void); +static void Android_JNI_ThreadDestroyed(void*); + +static pthread_key_t mThreadKey; +static JavaVM* mJavaVM; + +int Android_JNI_SetupThread(void) +{ + Android_JNI_GetEnv(); + return 1; +} + +/* Library init */ +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv *env; + mJavaVM = vm; + LOGI("JNI_OnLoad called"); + if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("Failed to get the environment using GetEnv()"); + return -1; + } + /* + * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread + * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this + */ + if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) { + + __android_log_print(ANDROID_LOG_ERROR, "pyjniusjni", "Error initializing pthread key"); + } + Android_JNI_SetupThread(); + + return JNI_VERSION_1_4; +} + +JNIEnv* Android_JNI_GetEnv(void) +{ + /* From http://developer.android.com/guide/practices/jni.html + * All threads are Linux threads, scheduled by the kernel. + * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then + * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the + * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, + * and cannot make JNI calls. + * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" + * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread + * is a no-op. + * Note: You can call this function any number of times for the same thread, there's no harm in it + */ + + JNIEnv *env; + int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); + if(status < 0) { + LOGE("failed to attach current thread"); + return 0; + } + + /* From http://developer.android.com/guide/practices/jni.html + * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, + * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be + * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific + * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) + * Note: The destructor is not called unless the stored value is != NULL + * Note: You can call this function any number of times for the same thread, there's no harm in it + * (except for some lost CPU cycles) + */ + pthread_setspecific(mThreadKey, (void*) env); + + return env; +} + +static void Android_JNI_ThreadDestroyed(void* value) +{ + /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ + JNIEnv *env = (JNIEnv*) value; + if (env != NULL) { + (*mJavaVM)->DetachCurrentThread(mJavaVM); + pthread_setspecific(mThreadKey, NULL); + } +} + +void *WebView_AndroidGetJNIEnv() +{ + return Android_JNI_GetEnv(); +} diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/start.c b/pythonforandroid/bootstraps/service_only/build/jni/src/start.c new file mode 100644 index 0000000000..34372d2ee2 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/start.c @@ -0,0 +1,355 @@ + +#define PY_SSIZE_T_CLEAN +#include "Python.h" +#ifndef Py_PYTHON_H +#error Python headers needed to compile C extensions, please install development version of Python. +#else + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "android/log.h" + +#define ENTRYPOINT_MAXLEN 128 +#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) +#define LOGP(x) LOG("python", (x)) + +static PyObject *androidembed_log(PyObject *self, PyObject *args) { + char *logstr = NULL; + if (!PyArg_ParseTuple(args, "s", &logstr)) { + return NULL; + } + LOG(getenv("PYTHON_NAME"), logstr); + Py_RETURN_NONE; +} + +static PyMethodDef AndroidEmbedMethods[] = { + {"log", androidembed_log, METH_VARARGS, "Log on android platform"}, + {NULL, NULL, 0, NULL}}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef androidembed = {PyModuleDef_HEAD_INIT, "androidembed", + "", -1, AndroidEmbedMethods}; + +PyMODINIT_FUNC initandroidembed(void) { + return PyModule_Create(&androidembed); +} +#else +PyMODINIT_FUNC initandroidembed(void) { + (void)Py_InitModule("androidembed", AndroidEmbedMethods); +} +#endif + +int dir_exists(char *filename) { + struct stat st; + if (stat(filename, &st) == 0) { + if (S_ISDIR(st.st_mode)) + return 1; + } + return 0; +} + +int file_exists(const char *filename) { + FILE *file; + if (file = fopen(filename, "r")) { + fclose(file); + return 1; + } + return 0; +} + +/* int main(int argc, char **argv) { */ +int main(int argc, char *argv[]) { + + char *env_argument = NULL; + char *env_entrypoint = NULL; + char *env_logname = NULL; + char entrypoint[ENTRYPOINT_MAXLEN]; + int ret = 0; + FILE *fd; + + /* AND: Several filepaths are hardcoded here, these must be made + configurable */ + /* AND: P4A uses env vars...not sure what's best */ + LOGP("Initialize Python for Android"); + env_argument = getenv("ANDROID_ARGUMENT"); + setenv("ANDROID_APP_PATH", env_argument, 1); + env_entrypoint = getenv("ANDROID_ENTRYPOINT"); + env_logname = getenv("PYTHON_NAME"); + + if (env_logname == NULL) { + env_logname = "python"; + setenv("PYTHON_NAME", "python", 1); + } + + LOGP("Changing directory to the one provided by ANDROID_ARGUMENT"); + LOGP(env_argument); + chdir(env_argument); + + Py_SetProgramName(L"android_python"); + +#if PY_MAJOR_VERSION >= 3 + /* our logging module for android + */ + PyImport_AppendInittab("androidembed", initandroidembed); +#endif + + LOGP("Preparing to initialize python"); + + if (dir_exists("crystax_python/")) { + LOGP("crystax_python exists"); + char paths[256]; + snprintf(paths, 256, + "%s/crystax_python/stdlib.zip:%s/crystax_python/modules", + env_argument, env_argument); + /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument, + * env_argument); */ + LOGP("calculated paths to be..."); + LOGP(paths); + +#if PY_MAJOR_VERSION >= 3 + wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL); + Py_SetPath(wchar_paths); +#else + char *wchar_paths = paths; + LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet"); + exit(1); +#endif + + LOGP("set wchar paths..."); + } else { + LOGP("crystax_python does not exist"); + } + + Py_Initialize(); + +#if PY_MAJOR_VERSION < 3 + PySys_SetArgv(argc, argv); +#endif + + LOGP("Initialized python"); + + /* ensure threads will work. + */ + LOGP("AND: Init threads"); + PyEval_InitThreads(); + +#if PY_MAJOR_VERSION < 3 + initandroidembed(); +#endif + + PyRun_SimpleString("import androidembed\nandroidembed.log('testing python " + "print redirection')"); + + /* inject our bootstrap code to redirect python stdin/stdout + * replace sys.path with our path + */ + PyRun_SimpleString("import sys, posix\n"); + if (dir_exists("lib")) { + /* If we built our own python, set up the paths correctly */ + LOGP("Setting up python from ANDROID_PRIVATE"); + PyRun_SimpleString("private = posix.environ['ANDROID_PRIVATE']\n" + "argument = posix.environ['ANDROID_ARGUMENT']\n" + "sys.path[:] = [ \n" + " private + '/lib/python27.zip', \n" + " private + '/lib/python2.7/', \n" + " private + '/lib/python2.7/lib-dynload/', \n" + " private + '/lib/python2.7/site-packages/', \n" + " argument ]\n"); + } + + if (dir_exists("crystax_python")) { + char add_site_packages_dir[256]; + snprintf(add_site_packages_dir, 256, + "sys.path.append('%s/crystax_python/site-packages')", + env_argument); + + PyRun_SimpleString("import sys\n" + "sys.argv = ['notaninterpreterreally']\n" + "from os.path import realpath, join, dirname"); + PyRun_SimpleString(add_site_packages_dir); + /* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */ + PyRun_SimpleString("sys.path = ['.'] + sys.path"); + } + + PyRun_SimpleString( + "class LogFile(object):\n" + " def __init__(self):\n" + " self.buffer = ''\n" + " def write(self, s):\n" + " s = self.buffer + s\n" + " lines = s.split(\"\\n\")\n" + " for l in lines[:-1]:\n" + " androidembed.log(l)\n" + " self.buffer = lines[-1]\n" + " def flush(self):\n" + " return\n" + "sys.stdout = sys.stderr = LogFile()\n" + "print('Android path', sys.path)\n" + "import os\n" + "print('os.environ is', os.environ)\n" + "print('Android kivy bootstrap done. __name__ is', __name__)"); + +#if PY_MAJOR_VERSION < 3 + PyRun_SimpleString("import site; print site.getsitepackages()\n"); +#endif + + LOGP("AND: Ran string"); + + /* run it ! + */ + LOGP("Run user program, change dir and execute entrypoint"); + + /* Get the entrypoint, search the .pyo then .py + */ + char *dot = strrchr(env_entrypoint, '.'); + if (dot <= 0) { + LOGP("Invalid entrypoint, abort."); + return -1; + } + if (strlen(env_entrypoint) > ENTRYPOINT_MAXLEN - 2) { + LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN."); + return -1; + } + if (!strcmp(dot, ".pyo")) { + if (!file_exists(env_entrypoint)) { + /* fallback on .py */ + strcpy(entrypoint, env_entrypoint); + entrypoint[strlen(env_entrypoint) - 1] = '\0'; + LOGP(entrypoint); + if (!file_exists(entrypoint)) { + LOGP("Entrypoint not found (.pyo, fallback on .py), abort"); + return -1; + } + } else { + strcpy(entrypoint, env_entrypoint); + } + } else if (!strcmp(dot, ".py")) { + /* if .py is passed, check the pyo version first */ + strcpy(entrypoint, env_entrypoint); + entrypoint[strlen(env_entrypoint) + 1] = '\0'; + entrypoint[strlen(env_entrypoint)] = 'o'; + if (!file_exists(entrypoint)) { + /* fallback on pure python version */ + if (!file_exists(env_entrypoint)) { + LOGP("Entrypoint not found (.py), abort."); + return -1; + } + strcpy(entrypoint, env_entrypoint); + } + } else { + LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort."); + return -1; + } + // LOGP("Entrypoint is:"); + // LOGP(entrypoint); + fd = fopen(entrypoint, "r"); + if (fd == NULL) { + LOGP("Open the entrypoint failed"); + LOGP(entrypoint); + return -1; + } + + /* run python ! + */ + ret = PyRun_SimpleFile(fd, entrypoint); + + if (PyErr_Occurred() != NULL) { + ret = 1; + PyErr_Print(); /* This exits with the right code if SystemExit. */ + PyObject *f = PySys_GetObject("stdout"); + if (PyFile_WriteString( + "\n", f)) /* python2 used Py_FlushLine, but this no longer exists */ + PyErr_Clear(); + } + + /* close everything + */ + Py_Finalize(); + fclose(fd); + + LOGP("Python for android ended."); + return ret; +} + +JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart( + JNIEnv *env, jobject thiz, jstring j_android_private, + jstring j_android_argument, jstring j_service_entrypoint, + jstring j_python_name, jstring j_python_home, jstring j_python_path, + jstring j_arg) { + jboolean iscopy; + const char *android_private = + (*env)->GetStringUTFChars(env, j_android_private, &iscopy); + const char *android_argument = + (*env)->GetStringUTFChars(env, j_android_argument, &iscopy); + const char *service_entrypoint = + (*env)->GetStringUTFChars(env, j_service_entrypoint, &iscopy); + const char *python_name = + (*env)->GetStringUTFChars(env, j_python_name, &iscopy); + const char *python_home = + (*env)->GetStringUTFChars(env, j_python_home, &iscopy); + const char *python_path = + (*env)->GetStringUTFChars(env, j_python_path, &iscopy); + const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy); + + setenv("ANDROID_PRIVATE", android_private, 1); + setenv("ANDROID_ARGUMENT", android_argument, 1); + setenv("ANDROID_ENTRYPOINT", service_entrypoint, 1); + setenv("PYTHONOPTIMIZE", "2", 1); + setenv("PYTHON_NAME", python_name, 1); + setenv("PYTHONHOME", python_home, 1); + setenv("PYTHONPATH", python_path, 1); + setenv("PYTHON_SERVICE_ARGUMENT", arg, 1); + + char *argv[] = {"."}; + /* ANDROID_ARGUMENT points to service subdir, + * so main() will run main.py from this dir + */ + main(1, argv); +} + +void Java_org_kivy_android_PythonActivity_nativeSetEnv( + JNIEnv* env, jclass jcls, + jstring j_name, jstring j_value) +/* JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv( */ +/* JNIEnv* env, jclass jcls, */ +/* jstring j_name, jstring j_value) */ +{ + jboolean iscopy; + const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy); + const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy); + setenv(name, value, 1); + (*env)->ReleaseStringUTFChars(env, j_name, name); + (*env)->ReleaseStringUTFChars(env, j_value, value); +} + + +void Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) +{ + /* This nativeInit follows SDL2 */ + + /* This interface could expand with ABI negotiation, calbacks, etc. */ + /* SDL_Android_Init(env, cls); */ + + /* SDL_SetMainReady(); */ + + /* Run the application code! */ + int status; + char *argv[2]; + argv[0] = "Python_app"; + argv[1] = NULL; + /* status = SDL_main(1, argv); */ + + main(1, argv); + + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ +} + +#endif diff --git a/pythonforandroid/bootstraps/service_only/build/proguard-project.txt b/pythonforandroid/bootstraps/service_only/build/proguard-project.txt new file mode 100644 index 0000000000..f2fe1559a2 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep b/pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png b/pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..59a00ba6fff07cec43a4100fdf22f3f679df2349 GIT binary patch literal 16525 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 L>T(q_W}*KFob!<> literal 0 HcmV?d00001 diff --git a/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml b/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml new file mode 100644 index 0000000000..123c4b6eac --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml b/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml new file mode 100644 index 0000000000..daebceb9d5 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml @@ -0,0 +1,5 @@ + + + SDL App + 0.1 + diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java new file mode 100755 index 0000000000..dd10624eab --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java @@ -0,0 +1,141 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class Octal { + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal string. + */ + public static long parseOctal(byte[] header, int offset, int length) { + long result = 0; + boolean stillPadding = true; + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + + if (header[i] == (byte) ' ' || header[i] == '0') { + if (stillPadding) + continue; + + if (header[i] == (byte) ' ') + break; + } + + stillPadding = false; + + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes(long value, byte[] buf, int offset, int length) { + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = (byte) ' '; + --idx; + + if (value == 0) { + buf[offset + idx] = (byte) '0'; + --idx; + } else { + for (long val = value; idx >= 0 && val > 0; --idx) { + buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) ); + val = val >> 3; + } + } + + for (; idx >= 0; --idx) { + buf[offset + idx] = (byte) ' '; + } + + return offset + length; + } + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) { + getOctalBytes( value, buf, offset, length ); + buf[offset + length - 1] = (byte) ' '; + buf[offset + length - 2] = 0; + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) { + byte[] temp = new byte[length + 1]; + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + return offset + length; + } + +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java new file mode 100755 index 0000000000..4611e20eaa --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java @@ -0,0 +1,28 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class TarConstants { + public static final int EOF_BLOCK = 1024; + public static final int DATA_BLOCK = 512; + public static final int HEADER_BLOCK = 512; +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java new file mode 100755 index 0000000000..fe01db463a --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java @@ -0,0 +1,284 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; +import java.util.Date; + +/** + * @author Kamran Zafar + * + */ +public class TarEntry { + protected File file; + protected TarHeader header; + + private TarEntry() { + this.file = null; + header = new TarHeader(); + } + + public TarEntry(File file, String entryName) { + this(); + this.file = file; + this.extractTarHeader(entryName); + } + + public TarEntry(byte[] headerBuf) { + this(); + this.parseTarHeader(headerBuf); + } + + /** + * Constructor to create an entry from an existing TarHeader object. + * + * This method is useful to add new entries programmatically (e.g. for + * adding files or directories that do not exist in the file system). + * + * @param header + * + */ + public TarEntry(TarHeader header) { + this.file = null; + this.header = header; + } + + public boolean equals(TarEntry it) { + return header.name.toString().equals(it.header.name.toString()); + } + + public boolean isDescendent(TarEntry desc) { + return desc.header.name.toString().startsWith(header.name.toString()); + } + + public TarHeader getHeader() { + return header; + } + + public String getName() { + String name = header.name.toString(); + if (header.namePrefix != null && !header.namePrefix.toString().equals("")) { + name = header.namePrefix.toString() + "/" + name; + } + + return name; + } + + public void setName(String name) { + header.name = new StringBuffer(name); + } + + public int getUserId() { + return header.userId; + } + + public void setUserId(int userId) { + header.userId = userId; + } + + public int getGroupId() { + return header.groupId; + } + + public void setGroupId(int groupId) { + header.groupId = groupId; + } + + public String getUserName() { + return header.userName.toString(); + } + + public void setUserName(String userName) { + header.userName = new StringBuffer(userName); + } + + public String getGroupName() { + return header.groupName.toString(); + } + + public void setGroupName(String groupName) { + header.groupName = new StringBuffer(groupName); + } + + public void setIds(int userId, int groupId) { + this.setUserId(userId); + this.setGroupId(groupId); + } + + public void setModTime(long time) { + header.modTime = time / 1000; + } + + public void setModTime(Date time) { + header.modTime = time.getTime() / 1000; + } + + public Date getModTime() { + return new Date(header.modTime * 1000); + } + + public File getFile() { + return this.file; + } + + public long getSize() { + return header.size; + } + + public void setSize(long size) { + header.size = size; + } + + /** + * Checks if the org.kamrazafar.jtar entry is a directory + * + * @return + */ + public boolean isDirectory() { + if (this.file != null) + return this.file.isDirectory(); + + if (header != null) { + if (header.linkFlag == TarHeader.LF_DIR) + return true; + + if (header.name.toString().endsWith("/")) + return true; + } + + return false; + } + + /** + * Extract header from File + * + * @param entryName + */ + public void extractTarHeader(String entryName) { + header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory()); + } + + /** + * Calculate checksum + * + * @param buf + * @return + */ + public long computeCheckSum(byte[] buf) { + long sum = 0; + + for (int i = 0; i < buf.length; ++i) { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Writes the header to the byte buffer + * + * @param outbuf + */ + public void writeEntryHeader(byte[] outbuf) { + int offset = 0; + + offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN); + offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN); + offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN); + offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN); + + long size = header.size; + + offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN); + offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN); + + int csOffset = offset; + for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) + outbuf[offset++] = (byte) ' '; + + outbuf[offset++] = header.linkFlag; + + offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN); + offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN); + offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN); + offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX); + + for (; offset < outbuf.length;) + outbuf[offset++] = 0; + + long checkSum = this.computeCheckSum(outbuf); + + Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN); + } + + /** + * Parses the tar header to the byte buffer + * + * @param header + * @param bh + */ + public void parseTarHeader(byte[] bh) { + int offset = 0; + + header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN); + offset += TarHeader.MODELEN; + + header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN); + offset += TarHeader.UIDLEN; + + header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN); + offset += TarHeader.GIDLEN; + + header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN); + offset += TarHeader.SIZELEN; + + header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN); + offset += TarHeader.MODTIMELEN; + + header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN); + offset += TarHeader.CHKSUMLEN; + + header.linkFlag = bh[offset++]; + + header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN); + offset += TarHeader.USTAR_MAGICLEN; + + header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN); + offset += TarHeader.USTAR_USER_NAMELEN; + + header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset += TarHeader.USTAR_GROUP_NAMELEN; + + header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX); + } +} \ No newline at end of file diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java new file mode 100755 index 0000000000..b9d3a86bef --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java @@ -0,0 +1,243 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * Header + * + *
+ * Offset  Size     Field
+ * 0       100      File name
+ * 100     8        File mode
+ * 108     8        Owner's numeric user ID
+ * 116     8        Group's numeric user ID
+ * 124     12       File size in bytes
+ * 136     12       Last modification time in numeric Unix time format
+ * 148     8        Checksum for header block
+ * 156     1        Link indicator (file type)
+ * 157     100      Name of linked file
+ * 
+ * + * + * File Types + * + *
+ * Value        Meaning
+ * '0'          Normal file
+ * (ASCII NUL)  Normal file (now obsolete)
+ * '1'          Hard link
+ * '2'          Symbolic link
+ * '3'          Character special
+ * '4'          Block special
+ * '5'          Directory
+ * '6'          FIFO
+ * '7'          Contigous
+ * 
+ * + * + * + * Ustar header + * + *
+ * Offset  Size    Field
+ * 257     6       UStar indicator "ustar"
+ * 263     2       UStar version "00"
+ * 265     32      Owner user name
+ * 297     32      Owner group name
+ * 329     8       Device major number
+ * 337     8       Device minor number
+ * 345     155     Filename prefix
+ * 
+ */ + +public class TarHeader { + + /* + * Header + */ + public static final int NAMELEN = 100; + public static final int MODELEN = 8; + public static final int UIDLEN = 8; + public static final int GIDLEN = 8; + public static final int SIZELEN = 12; + public static final int MODTIMELEN = 12; + public static final int CHKSUMLEN = 8; + public static final byte LF_OLDNORM = 0; + + /* + * File Types + */ + public static final byte LF_NORMAL = (byte) '0'; + public static final byte LF_LINK = (byte) '1'; + public static final byte LF_SYMLINK = (byte) '2'; + public static final byte LF_CHR = (byte) '3'; + public static final byte LF_BLK = (byte) '4'; + public static final byte LF_DIR = (byte) '5'; + public static final byte LF_FIFO = (byte) '6'; + public static final byte LF_CONTIG = (byte) '7'; + + /* + * Ustar header + */ + + public static final String USTAR_MAGIC = "ustar"; // POSIX + + public static final int USTAR_MAGICLEN = 8; + public static final int USTAR_USER_NAMELEN = 32; + public static final int USTAR_GROUP_NAMELEN = 32; + public static final int USTAR_DEVLEN = 8; + public static final int USTAR_FILENAME_PREFIX = 155; + + // Header values + public StringBuffer name; + public int mode; + public int userId; + public int groupId; + public long size; + public long modTime; + public int checkSum; + public byte linkFlag; + public StringBuffer linkName; + public StringBuffer magic; // ustar indicator and version + public StringBuffer userName; + public StringBuffer groupName; + public int devMajor; + public int devMinor; + public StringBuffer namePrefix; + + public TarHeader() { + this.magic = new StringBuffer(TarHeader.USTAR_MAGIC); + + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty("user.name", ""); + + if (user.length() > 31) + user = user.substring(0, 31); + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer(user); + this.groupName = new StringBuffer(""); + this.namePrefix = new StringBuffer(); + } + + /** + * Parse an entry name from a header buffer. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName(byte[] header, int offset, int length) { + StringBuffer result = new StringBuffer(length); + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + result.append((char) header[i]); + } + + return result; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) { + int i; + + for (i = 0; i < length && i < name.length(); ++i) { + buf[offset + i] = (byte) name.charAt(i); + } + + for (; i < length; ++i) { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Creates a new header for a file/directory entry. + * + * + * @param name + * File name + * @param size + * File size in bytes + * @param modTime + * Last modification time in numeric Unix time format + * @param dir + * Is directory + * + * @return + */ + public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) { + String name = entryName; + name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/'); + + TarHeader header = new TarHeader(); + header.linkName = new StringBuffer(""); + + if (name.length() > 100) { + header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/'))); + header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1)); + } else { + header.name = new StringBuffer(name); + } + + if (dir) { + header.mode = 040755; + header.linkFlag = TarHeader.LF_DIR; + if (header.name.charAt(header.name.length() - 1) != '/') { + header.name.append("/"); + } + header.size = 0; + } else { + header.mode = 0100644; + header.linkFlag = TarHeader.LF_NORMAL; + header.size = size; + } + + header.modTime = modTime; + header.checkSum = 0; + header.devMajor = 0; + header.devMinor = 0; + + return header; + } +} \ No newline at end of file diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java new file mode 100755 index 0000000000..ec50a1b688 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java @@ -0,0 +1,249 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Kamran Zafar + * + */ +public class TarInputStream extends FilterInputStream { + + private static final int SKIP_BUFFER_SIZE = 2048; + private TarEntry currentEntry; + private long currentFileSize; + private long bytesRead; + private boolean defaultSkip = false; + + public TarInputStream(InputStream in) { + super(in); + currentFileSize = 0; + bytesRead = 0; + } + + @Override + public boolean markSupported() { + return false; + } + + /** + * Not supported + * + */ + @Override + public synchronized void mark(int readlimit) { + } + + /** + * Not supported + * + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + /** + * Read a byte + * + * @see java.io.FilterInputStream#read() + */ + @Override + public int read() throws IOException { + byte[] buf = new byte[1]; + + int res = this.read(buf, 0, 1); + + if (res != -1) { + return 0xFF & buf[0]; + } + + return res; + } + + /** + * Checks if the bytes being read exceed the entry size and adjusts the byte + * array length. Updates the byte counters + * + * + * @see java.io.FilterInputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (currentEntry != null) { + if (currentFileSize == currentEntry.getSize()) { + return -1; + } else if ((currentEntry.getSize() - currentFileSize) < len) { + len = (int) (currentEntry.getSize() - currentFileSize); + } + } + + int br = super.read(b, off, len); + + if (br != -1) { + if (currentEntry != null) { + currentFileSize += br; + } + + bytesRead += br; + } + + return br; + } + + /** + * Returns the next entry in the tar file + * + * @return TarEntry + * @throws IOException + */ + public TarEntry getNextEntry() throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + byte[] theader = new byte[TarConstants.HEADER_BLOCK]; + int tr = 0; + + // Read full header + while (tr < TarConstants.HEADER_BLOCK) { + int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr); + + if (res < 0) { + break; + } + + System.arraycopy(theader, 0, header, tr, res); + tr += res; + } + + // Check if record is null + boolean eof = true; + for (byte b : header) { + if (b != 0) { + eof = false; + break; + } + } + + if (!eof) { + currentEntry = new TarEntry(header); + } + + return currentEntry; + } + + /** + * Returns the current offset (in bytes) from the beginning of the stream. + * This can be used to find out at which point in a tar file an entry's content begins, for instance. + */ + public long getCurrentOffset() { + return bytesRead; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + // Not fully read, skip rest of the bytes + long bs = 0; + while (bs < currentEntry.getSize() - currentFileSize) { + long res = skip(currentEntry.getSize() - currentFileSize - bs); + + if (res == 0 && currentEntry.getSize() - currentFileSize > 0) { + // I suspect file corruption + throw new IOException("Possible tar file corruption"); + } + + bs += res; + } + } + + currentEntry = null; + currentFileSize = 0L; + skipPad(); + } + } + + /** + * Skips the pad at the end of each tar entry file content + * + * @throws IOException + */ + protected void skipPad() throws IOException { + if (bytesRead > 0) { + int extra = (int) (bytesRead % TarConstants.DATA_BLOCK); + + if (extra > 0) { + long bs = 0; + while (bs < TarConstants.DATA_BLOCK - extra) { + long res = skip(TarConstants.DATA_BLOCK - extra - bs); + bs += res; + } + } + } + } + + /** + * Skips 'n' bytes on the InputStream
+ * Overrides default implementation of skip + * + */ + @Override + public long skip(long n) throws IOException { + if (defaultSkip) { + // use skip method of parent stream + // may not work if skip not implemented by parent + long bs = super.skip(n); + bytesRead += bs; + + return bs; + } + + if (n <= 0) { + return 0; + } + + long left = n; + byte[] sBuff = new byte[SKIP_BUFFER_SIZE]; + + while (left > 0) { + int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE)); + if (res < 0) { + break; + } + left -= res; + } + + return n - left; + } + + public boolean isDefaultSkip() { + return defaultSkip; + } + + public void setDefaultSkip(boolean defaultSkip) { + this.defaultSkip = defaultSkip; + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java new file mode 100755 index 0000000000..ffdfe87564 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java @@ -0,0 +1,163 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +/** + * @author Kamran Zafar + * + */ +public class TarOutputStream extends OutputStream { + private final OutputStream out; + private long bytesWritten; + private long currentFileSize; + private TarEntry currentEntry; + + public TarOutputStream(OutputStream out) { + this.out = out; + bytesWritten = 0; + currentFileSize = 0; + } + + public TarOutputStream(final File fout) throws FileNotFoundException { + this.out = new BufferedOutputStream(new FileOutputStream(fout)); + bytesWritten = 0; + currentFileSize = 0; + } + + /** + * Opens a file for writing. + */ + public TarOutputStream(final File fout, final boolean append) throws IOException { + @SuppressWarnings("resource") + RandomAccessFile raf = new RandomAccessFile(fout, "rw"); + final long fileSize = fout.length(); + if (append && fileSize > TarConstants.EOF_BLOCK) { + raf.seek(fileSize - TarConstants.EOF_BLOCK); + } + out = new BufferedOutputStream(new FileOutputStream(raf.getFD())); + } + + /** + * Appends the EOF record and closes the stream + * + * @see java.io.FilterOutputStream#close() + */ + @Override + public void close() throws IOException { + closeCurrentEntry(); + write( new byte[TarConstants.EOF_BLOCK] ); + out.close(); + } + /** + * Writes a byte to the stream and updates byte counters + * + * @see java.io.FilterOutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + out.write( b ); + bytesWritten += 1; + + if (currentEntry != null) { + currentFileSize += 1; + } + } + + /** + * Checks if the bytes being written exceed the current entry size. + * + * @see java.io.FilterOutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (currentEntry != null && !currentEntry.isDirectory()) { + if (currentEntry.getSize() < currentFileSize + len) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] size[" + + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len ) + + "] being written." ); + } + } + + out.write( b, off, len ); + + bytesWritten += len; + + if (currentEntry != null) { + currentFileSize += len; + } + } + + /** + * Writes the next tar entry header on the stream + * + * @param entry + * @throws IOException + */ + public void putNextEntry(TarEntry entry) throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + entry.writeEntryHeader( header ); + + write( header ); + + currentEntry = entry; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] of size[" + + currentEntry.getSize() + "] has not been fully written." ); + } + + currentEntry = null; + currentFileSize = 0; + + pad(); + } + } + + /** + * Pads the last content block + * + * @throws IOException + */ + protected void pad() throws IOException { + if (bytesWritten > 0) { + int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK ); + + if (extra > 0) { + write( new byte[TarConstants.DATA_BLOCK - extra] ); + } + } + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java new file mode 100755 index 0000000000..50165765c0 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java @@ -0,0 +1,96 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * @author Kamran + * + */ +public class TarUtils { + /** + * Determines the tar file size of the given folder/file path + * + * @param path + * @return + */ + public static long calculateTarSize(File path) { + return tarSize(path) + TarConstants.EOF_BLOCK; + } + + private static long tarSize(File dir) { + long size = 0; + + if (dir.isFile()) { + return entrySize(dir.length()); + } else { + File[] subFiles = dir.listFiles(); + + if (subFiles != null && subFiles.length > 0) { + for (File file : subFiles) { + if (file.isFile()) { + size += entrySize(file.length()); + } else { + size += tarSize(file); + } + } + } else { + // Empty folder header + return TarConstants.HEADER_BLOCK; + } + } + + return size; + } + + private static long entrySize(long fileSize) { + long size = 0; + size += TarConstants.HEADER_BLOCK; // Header + size += fileSize; // File size + + long extra = size % TarConstants.DATA_BLOCK; + + if (extra > 0) { + size += (TarConstants.DATA_BLOCK - extra); // pad + } + + return size; + } + + public static String trim(String s, char c) { + StringBuffer tmp = new StringBuffer(s); + for (int i = 0; i < tmp.length(); i++) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + for (int i = tmp.length() - 1; i >= 0; i--) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + return tmp.toString(); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java new file mode 100644 index 0000000000..58a1c5edf8 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java @@ -0,0 +1,19 @@ +package org.kivy.android; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class GenericBroadcastReceiver extends BroadcastReceiver { + + GenericBroadcastReceiverCallback listener; + + public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) { + super(); + this.listener = listener; + } + + public void onReceive(Context context, Intent intent) { + this.listener.onReceive(context, intent); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java new file mode 100644 index 0000000000..1a87c98b2d --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java @@ -0,0 +1,8 @@ +package org.kivy.android; + +import android.content.Intent; +import android.content.Context; + +public interface GenericBroadcastReceiverCallback { + void onReceive(Context context, Intent intent); +}; diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java new file mode 100644 index 0000000000..ba00ab36f2 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java @@ -0,0 +1,393 @@ + +package org.kivy.android; + +import java.net.Socket; +import java.net.InetSocketAddress; + +import android.os.SystemClock; + +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +import android.app.*; +import android.content.*; +import android.view.*; +import android.view.ViewGroup; +import android.view.SurfaceView; +import android.app.Activity; +import android.content.Intent; +import android.util.Log; +import android.widget.Toast; +import android.os.Bundle; +import android.os.PowerManager; +import android.graphics.PixelFormat; +import android.view.SurfaceHolder; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; +import android.content.Intent; +import android.widget.ImageView; +import java.io.InputStream; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import android.widget.AbsoluteLayout; +import android.view.ViewGroup.LayoutParams; + +import android.webkit.WebViewClient; +import android.webkit.WebView; + +import org.kivy.android.PythonUtil; + +import org.kivy.android.WebViewLoader; + +import org.renpy.android.ResourceManager; +import org.renpy.android.AssetExtract; + +public class PythonActivity extends Activity { + // This activity is modified from a mixture of the SDLActivity and + // PythonActivity in the SDL2 bootstrap, but removing all the SDL2 + // specifics. + + private static final String TAG = "PythonActivity"; + + public static PythonActivity mActivity = null; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + protected static ViewGroup mLayout; + protected static WebView mWebView; + + protected static Thread mPythonThread; + + private ResourceManager resourceManager = null; + private Bundle mMetaData = null; + private PowerManager.WakeLock mWakeLock = null; + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mWebView = null; + mLayout = null; + mBrokenLibraries = false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "My oncreate running"); + resourceManager = new ResourceManager(this); + + Log.v(TAG, "Ready to unpack"); + unpackData("private", getFilesDir()); + + this.mActivity = this; + + Log.v("Python", "Device: " + android.os.Build.DEVICE); + Log.v("Python", "Model: " + android.os.Build.MODEL); + super.onCreate(savedInstanceState); + + PythonActivity.initialize(); + + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to load the application libraries. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("Python Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + PythonActivity.mActivity.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up the webview + mWebView = new WebView(this); + mWebView.getSettings().setJavaScriptEnabled(true); + mWebView.getSettings().setDomStorageEnabled(true); + mWebView.loadUrl("file:///" + mActivity.getFilesDir().getAbsolutePath() + "/_load.html"); + + mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + mWebView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + view.loadUrl(url); + return false; + } + }); + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mWebView); + + setContentView(mLayout); + + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); + Log.v(TAG, "Setting env vars for start.c and Python to use"); + PythonActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + PythonActivity.nativeSetEnv("ANDROID_ARGUMENT", mFilesDirectory); + PythonActivity.nativeSetEnv("ANDROID_APP_PATH", mFilesDirectory); + PythonActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo"); + PythonActivity.nativeSetEnv("PYTHONHOME", mFilesDirectory); + PythonActivity.nativeSetEnv("PYTHONPATH", mFilesDirectory + ":" + mFilesDirectory + "/lib"); + + try { + Log.v(TAG, "Access to our meta-data..."); + this.mMetaData = this.mActivity.getPackageManager().getApplicationInfo( + this.mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) this.mActivity.getSystemService(Context.POWER_SERVICE); + if ( this.mMetaData.getInt("wakelock") == 1 ) { + this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + } + } catch (PackageManager.NameNotFoundException e) { + } + + final Thread pythonThread = new Thread(new PythonMain(), "PythonThread"); + PythonActivity.mPythonThread = pythonThread; + pythonThread.start(); + + final Thread wvThread = new Thread(new WebViewLoaderMain(), "WvThread"); + wvThread.start(); + + } + + public void loadLibraries() { + PythonUtil.loadLibraries(getFilesDir()); + } + + public void recursiveDelete(File f) { + if (f.isDirectory()) { + for (File r : f.listFiles()) { + recursiveDelete(r); + } + } + f.delete(); + } + + /** + * Show an error using a toast. (Only makes sense from non-UI + * threads.) + */ + public void toastError(final String msg) { + + final Activity thisActivity = this; + + runOnUiThread(new Runnable () { + public void run() { + Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show(); + } + }); + + // Wait to show the error. + synchronized (this) { + try { + this.wait(1000); + } catch (InterruptedException e) { + } + } + } + + public void unpackData(final String resource, File target) { + + Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); + + // The version of data in memory and on disk. + String data_version = resourceManager.getString(resource + "_version"); + String disk_version = null; + + Log.v(TAG, "Data version is " + data_version); + + // If no version, no unpacking is necessary. + if (data_version == null) { + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String disk_version_fn = filesDir + "/" + resource + ".version"; + + try { + byte buf[] = new byte[64]; + InputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + disk_version = ""; + } + + // If the disk data is out of date, extract it and write the + // version file. + // if (! data_version.equals(disk_version)) { + if (! data_version.equals(disk_version)) { + Log.v(TAG, "Extracting " + resource + " assets."); + + recursiveDelete(target); + target.mkdirs(); + + AssetExtract ae = new AssetExtract(this); + if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { + toastError("Could not extract " + resource + " data."); + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(disk_version_fn); + os.write(data_version.getBytes()); + os.close(); + } catch (Exception e) { + Log.w("python", e); + } + } + } + + public static ViewGroup getLayout() { + return mLayout; + } + + + //---------------------------------------------------------------------------- + // Listener interface for onNewIntent + // + + public interface NewIntentListener { + void onNewIntent(Intent intent); + } + + private List newIntentListeners = null; + + public void registerNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + this.newIntentListeners = Collections.synchronizedList(new ArrayList()); + this.newIntentListeners.add(listener); + } + + public void unregisterNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + return; + this.newIntentListeners.remove(listener); + } + + @Override + protected void onNewIntent(Intent intent) { + if ( this.newIntentListeners == null ) + return; + this.onResume(); + synchronized ( this.newIntentListeners ) { + Iterator iterator = this.newIntentListeners.iterator(); + while ( iterator.hasNext() ) { + (iterator.next()).onNewIntent(intent); + } + } + } + + //---------------------------------------------------------------------------- + // Listener interface for onActivityResult + // + + public interface ActivityResultListener { + void onActivityResult(int requestCode, int resultCode, Intent data); + } + + private List activityResultListeners = null; + + public void registerActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + this.activityResultListeners = Collections.synchronizedList(new ArrayList()); + this.activityResultListeners.add(listener); + } + + public void unregisterActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + return; + this.activityResultListeners.remove(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if ( this.activityResultListeners == null ) + return; + this.onResume(); + synchronized ( this.activityResultListeners ) { + Iterator iterator = this.activityResultListeners.iterator(); + while ( iterator.hasNext() ) + (iterator.next()).onActivityResult(requestCode, resultCode, intent); + } + } + + public static void start_service(String serviceTitle, String serviceDescription, + String pythonServiceArgument) { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath(); + String filesDirectory = argument; + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", argument); + serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo"); + serviceIntent.putExtra("pythonHome", argument); + serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/lib"); + serviceIntent.putExtra("serviceTitle", serviceTitle); + serviceIntent.putExtra("serviceDescription", serviceDescription); + serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument); + PythonActivity.mActivity.startService(serviceIntent); + } + + public static void stop_service() { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + PythonActivity.mActivity.stopService(serviceIntent); + } + + + public static native void nativeSetEnv(String j_name, String j_value); + public static native int nativeInit(Object arguments); + +} + + +class PythonMain implements Runnable { + @Override + public void run() { + PythonActivity.nativeInit(new String[0]); + } +} + +class WebViewLoaderMain implements Runnable { + @Override + public void run() { + WebViewLoader.testConnection(); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java new file mode 100644 index 0000000000..f8dde3e0d2 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java @@ -0,0 +1,129 @@ +package org.kivy.android; + +import android.app.Service; +import android.os.IBinder; +import android.os.Bundle; +import android.content.Intent; +import android.content.Context; +import android.util.Log; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Process; + +import org.kivy.android.PythonUtil; + +import org.renpy.android.Hardware; + + +public class PythonService extends Service implements Runnable { + + // Thread for Python code + private Thread pythonThread = null; + + // Python environment variables + private String androidPrivate; + private String androidArgument; + private String pythonName; + private String pythonHome; + private String pythonPath; + private String serviceEntrypoint; + // Argument to pass to Python code, + private String pythonServiceArgument; + public static PythonService mService = null; + private Intent startIntent = null; + + private boolean autoRestartService = false; + + public void setAutoRestartService(boolean restart) { + autoRestartService = restart; + } + + public boolean canDisplayNotification() { + return true; + } + + public int startType() { + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (pythonThread != null) { + Log.v("python service", "service exists, do not start again"); + return START_NOT_STICKY; + } + + startIntent = intent; + Bundle extras = intent.getExtras(); + androidPrivate = extras.getString("androidPrivate"); + androidArgument = extras.getString("androidArgument"); + serviceEntrypoint = extras.getString("serviceEntrypoint"); + pythonName = extras.getString("pythonName"); + pythonHome = extras.getString("pythonHome"); + pythonPath = extras.getString("pythonPath"); + pythonServiceArgument = extras.getString("pythonServiceArgument"); + + pythonThread = new Thread(this); + pythonThread.start(); + + if (canDisplayNotification()) { + doStartForeground(extras); + } + + return startType(); + } + + protected void doStartForeground(Bundle extras) { + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + Context context = getApplicationContext(); + Notification notification = new Notification(context.getApplicationInfo().icon, + serviceTitle, System.currentTimeMillis()); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + notification.setLatestEventInfo(context, serviceTitle, serviceDescription, pIntent); + startForeground(1, notification); + } + + @Override + public void onDestroy() { + super.onDestroy(); + pythonThread = null; + if (autoRestartService && startIntent != null) { + Log.v("python service", "service restart requested"); + startService(startIntent); + } + Process.killProcess(Process.myPid()); + } + + @Override + public void run(){ + PythonUtil.loadLibraries(getFilesDir()); + this.mService = this; + nativeStart( + androidPrivate, androidArgument, + serviceEntrypoint, pythonName, + pythonHome, pythonPath, + pythonServiceArgument); + stopSelf(); + } + + // Native part + public static native void nativeStart( + String androidPrivate, String androidArgument, + String serviceEntrypoint, String pythonName, + String pythonHome, String pythonPath, + String pythonServiceArgument); +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java new file mode 100644 index 0000000000..9d532b613f --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java @@ -0,0 +1,56 @@ +package org.kivy.android; + +import java.io.File; + +import android.util.Log; + + +public class PythonUtil { + private static final String TAG = "PythonUtil"; + + protected static String[] getLibraries() { + return new String[] { + // "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_ttf", + "python2.7", + "python3.5m", + "main" + }; + } + + public static void loadLibraries(File filesDir) { + + String filesDirPath = filesDir.getAbsolutePath(); + boolean skippedPython = false; + + for (String lib : getLibraries()) { + try { + System.loadLibrary(lib); + } catch(UnsatisfiedLinkError e) { + if (lib.startsWith("python") && !skippedPython) { + skippedPython = true; + continue; + } + throw e; + } + } + + try { + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so"); + System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so"); + } catch(UnsatisfiedLinkError e) { + Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay."); + } + + try { + // System.loadLibrary("ctypes"); + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so"); + } catch(UnsatisfiedLinkError e) { + Log.v(TAG, "Unsatisfied linker when loading ctypes"); + } + + Log.v(TAG, "Loaded everything!"); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java new file mode 100644 index 0000000000..9911356ba0 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java @@ -0,0 +1,45 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonEvent { + private final Lock lock = new ReentrantLock(); + private final Condition cond = lock.newCondition(); + private boolean flag = false; + + public void set() { + lock.lock(); + try { + flag = true; + cond.signalAll(); + } finally { + lock.unlock(); + } + } + + public void wait_() throws InterruptedException { + lock.lock(); + try { + while (!flag) { + cond.await(); + } + } finally { + lock.unlock(); + } + } + + public void clear() { + lock.lock(); + try { + flag = false; + cond.signalAll(); + } finally { + lock.unlock(); + } + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java new file mode 100644 index 0000000000..22f9d903e1 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java @@ -0,0 +1,19 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonLock { + private final Lock lock = new ReentrantLock(); + + public void acquire() { + lock.lock(); + } + + public void release() { + lock.unlock(); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java new file mode 100644 index 0000000000..52d6424e09 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java @@ -0,0 +1,115 @@ +// This string is autogenerated by ChangeAppSettings.sh, do not change +// spaces amount +package org.renpy.android; + +import java.io.*; + +import android.app.Activity; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.File; + +import java.util.zip.GZIPInputStream; + +import android.content.res.AssetManager; + +import org.kamranzafar.jtar.*; + +public class AssetExtract { + + private AssetManager mAssetManager = null; + private Activity mActivity = null; + + public AssetExtract(Activity act) { + mActivity = act; + mAssetManager = act.getAssets(); + } + + public boolean extractTar(String asset, String target) { + + byte buf[] = new byte[1024 * 1024]; + + InputStream assetStream = null; + TarInputStream tis = null; + + try { + assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING); + tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192)); + } catch (IOException e) { + Log.e("python", "opening up extract tar", e); + return false; + } + + while (true) { + TarEntry entry = null; + + try { + entry = tis.getNextEntry(); + } catch ( java.io.IOException e ) { + Log.e("python", "extracting tar", e); + return false; + } + + if ( entry == null ) { + break; + } + + Log.v("python", "extracting " + entry.getName()); + + if (entry.isDirectory()) { + + try { + new File(target +"/" + entry.getName()).mkdirs(); + } catch ( SecurityException e ) { }; + + continue; + } + + OutputStream out = null; + String path = target + "/" + entry.getName(); + + try { + out = new BufferedOutputStream(new FileOutputStream(path), 8192); + } catch ( FileNotFoundException e ) { + } catch ( SecurityException e ) { }; + + if ( out == null ) { + Log.e("python", "could not open " + path); + return false; + } + + try { + while (true) { + int len = tis.read(buf); + + if (len == -1) { + break; + } + + out.write(buf, 0, len); + } + + out.flush(); + out.close(); + } catch ( java.io.IOException e ) { + Log.e("python", "extracting zip", e); + return false; + } + } + + try { + tis.close(); + assetStream.close(); + } catch (IOException e) { + // pass + } + + return true; + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java new file mode 100644 index 0000000000..c50692d71d --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java @@ -0,0 +1,287 @@ +package org.renpy.android; + +import android.content.Context; +import android.os.Vibrator; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.DisplayMetrics; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.EditorInfo; +import android.view.View; + +import java.util.List; +import java.util.ArrayList; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import org.kivy.android.PythonActivity; + +/** + * Methods that are expected to be called via JNI, to access the + * device's non-screen hardware. (For example, the vibration and + * accelerometer.) + */ +public class Hardware { + + // The context. + static Context context; + static View view; + + /** + * Vibrate for s seconds. + */ + public static void vibrate(double s) { + Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate((int) (1000 * s)); + } + } + + /** + * Get an Overview of all Hardware Sensors of an Android Device + */ + public static String getHardwareSensors() { + SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + List allSensors = sm.getSensorList(Sensor.TYPE_ALL); + + if (allSensors != null) { + String resultString = ""; + for (Sensor s : allSensors) { + resultString += String.format("Name=" + s.getName()); + resultString += String.format(",Vendor=" + s.getVendor()); + resultString += String.format(",Version=" + s.getVersion()); + resultString += String.format(",MaximumRange=" + s.getMaximumRange()); + // XXX MinDelay is not in the 2.2 + //resultString += String.format(",MinDelay=" + s.getMinDelay()); + resultString += String.format(",Power=" + s.getPower()); + resultString += String.format(",Type=" + s.getType() + "\n"); + } + return resultString; + } + return ""; + } + + + /** + * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors + */ + public static class generic3AxisSensor implements SensorEventListener { + private final SensorManager sSensorManager; + private final Sensor sSensor; + private final int sSensorType; + SensorEvent sSensorEvent; + + public generic3AxisSensor(int sensorType) { + sSensorType = sensorType; + sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + sSensor = sSensorManager.getDefaultSensor(sSensorType); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + + public void onSensorChanged(SensorEvent event) { + sSensorEvent = event; + } + + /** + * Enable or disable the Sensor by registering/unregistering + */ + public void changeStatus(boolean enable) { + if (enable) { + sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL); + } else { + sSensorManager.unregisterListener(this, sSensor); + } + } + + /** + * Read the Sensor + */ + public float[] readSensor() { + if (sSensorEvent != null) { + return sSensorEvent.values; + } else { + float rv[] = { 0f, 0f, 0f }; + return rv; + } + } + } + + public static generic3AxisSensor accelerometerSensor = null; + public static generic3AxisSensor orientationSensor = null; + public static generic3AxisSensor magneticFieldSensor = null; + + /** + * functions for backward compatibility reasons + */ + + public static void accelerometerEnable(boolean enable) { + if ( accelerometerSensor == null ) + accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER); + accelerometerSensor.changeStatus(enable); + } + public static float[] accelerometerReading() { + float rv[] = { 0f, 0f, 0f }; + if ( accelerometerSensor == null ) + return rv; + return (float[]) accelerometerSensor.readSensor(); + } + public static void orientationSensorEnable(boolean enable) { + if ( orientationSensor == null ) + orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION); + orientationSensor.changeStatus(enable); + } + public static float[] orientationSensorReading() { + float rv[] = { 0f, 0f, 0f }; + if ( orientationSensor == null ) + return rv; + return (float[]) orientationSensor.readSensor(); + } + public static void magneticFieldSensorEnable(boolean enable) { + if ( magneticFieldSensor == null ) + magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD); + magneticFieldSensor.changeStatus(enable); + } + public static float[] magneticFieldSensorReading() { + float rv[] = { 0f, 0f, 0f }; + if ( magneticFieldSensor == null ) + return rv; + return (float[]) magneticFieldSensor.readSensor(); + } + + static public DisplayMetrics metrics = new DisplayMetrics(); + + /** + * Get display DPI. + */ + public static int getDPI() { + // AND: Shouldn't have to get the metrics like this every time... + PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + return metrics.densityDpi; + } + + // /** + // * Show the soft keyboard. + // */ + // public static void showKeyboard(int input_type) { + // //Log.i("python", "hardware.Java show_keyword " input_type); + + // InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + + // SDLSurfaceView vw = (SDLSurfaceView) view; + + // int inputType = input_type; + + // if (vw.inputType != inputType){ + // vw.inputType = inputType; + // imm.restartInput(view); + // } + + // imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); + // } + + /** + * Hide the soft keyboard. + */ + public static void hideKeyboard() { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + /** + * Scan WiFi networks + */ + static List latestResult; + + public static void enableWifiScanner() + { + IntentFilter i = new IntentFilter(); + i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + + context.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context c, Intent i) { + // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs + WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE); + latestResult = w.getScanResults(); // Returns a of scanResults + } + + }, i); + + } + + public static String scanWifi() { + + // Now you can call this and it should execute the broadcastReceiver's + // onReceive() + WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + boolean a = wm.startScan(); + + if (latestResult != null){ + + String latestResultString = ""; + for (ScanResult result : latestResult) + { + latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level); + } + + return latestResultString; + } + + return ""; + } + + /** + * network state + */ + + public static boolean network_state = false; + + /** + * Check network state directly + * + * (only one connection can be active at a given moment, detects all network type) + * + */ + public static boolean checkNetwork() + { + boolean state = false; + final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo(); + if (activeNetwork != null && activeNetwork.isConnected()) { + state = true; + } else { + state = false; + } + + return state; + } + + /** + * To recieve network state changes + */ + public static void registerNetworkCheck() + { + IntentFilter i = new IntentFilter(); + i.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context c, Intent i) { + network_state = checkNetwork(); + } + + }, i); + } + +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java new file mode 100644 index 0000000000..0d34d31c9a --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java @@ -0,0 +1,12 @@ +package org.renpy.android; + +import android.util.Log; + +class PythonActivity extends org.kivy.android.PythonActivity { + static { + Log.w("PythonActivity", "Accessing org.renpy.android.PythonActivity " + + "is deprecated and will be removed in a " + + "future version. Please switch to " + + "org.kivy.android.PythonActivity."); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java new file mode 100644 index 0000000000..73febed68a --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java @@ -0,0 +1,12 @@ +package org.renpy.android; + +import android.util.Log; + +class PythonService extends org.kivy.android.PythonService { + static { + Log.w("PythonService", "Accessing org.renpy.android.PythonService " + + "is deprecated and will be removed in a " + + "future version. Please switch to " + + "org.kivy.android.PythonService."); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java new file mode 100644 index 0000000000..47455abb04 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java @@ -0,0 +1,54 @@ +/** + * This class takes care of managing resources for us. In our code, we + * can't use R, since the name of the package containing R will + * change. (This same code is used in both org.renpy.android and + * org.renpy.pygame.) So this is the next best thing. + */ + +package org.renpy.android; + +import android.app.Activity; +import android.content.res.Resources; +import android.view.View; + +import android.util.Log; + +public class ResourceManager { + + private Activity act; + private Resources res; + + public ResourceManager(Activity activity) { + act = activity; + res = act.getResources(); + } + + public int getIdentifier(String name, String kind) { + Log.v("SDL", "getting identifier"); + Log.v("SDL", "kind is " + kind + " and name " + name); + Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName())); + return res.getIdentifier(name, kind, act.getPackageName()); + } + + public String getString(String name) { + + try { + Log.v("SDL", "asked to get string " + name); + return res.getString(getIdentifier(name, "string")); + } catch (Exception e) { + Log.v("SDL", "got exception looking for string!"); + return null; + } + } + + public View inflateView(String name) { + int id = getIdentifier(name, "layout"); + return act.getLayoutInflater().inflate(id, null); + } + + public View getViewById(View v, String name) { + int id = getIdentifier(name, "id"); + return v.findViewById(id); + } + +} diff --git a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml new file mode 100644 index 0000000000..079638e0e9 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml @@ -0,0 +1,95 @@ + + + + = 9 %} + android:xlargeScreens="true" + {% endif %} + /> + + + + + + + {% for perm in args.permissions %} + {% if '.' in perm %} + + {% else %} + + {% endif %} + {% endfor %} + + {% if args.wakelock %} + + {% endif %} + + {% if args.billing_pubkey %} + + {% endif %} + + + + + {% for m in args.meta_data %} + {% endfor %} + + + + + + + + {%- if args.intent_filters -%} + {{- args.intent_filters -}} + {%- endif -%} + + + {% if service %} + + {% endif %} + {% for name in service_names %} + + {% endfor %} + + {% if args.billing_pubkey %} + + + + + + + + + {% endif %} + + + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java new file mode 100644 index 0000000000..bf87996212 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java @@ -0,0 +1,56 @@ +package {{ args.package }}; + +import android.content.Intent; +import android.content.Context; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Bundle; +import org.kivy.android.PythonService; +import org.kivy.android.PythonActivity; + + +public class Service{{ name|capitalize }} extends PythonService { + {% if sticky %} + @Override + public int startType() { + return START_STICKY; + } + {% endif %} + + {% if not foreground %} + @Override + public boolean canDisplayNotification() { + return false; + } + {% endif %} + + @Override + protected void doStartForeground(Bundle extras) { + Context context = getApplicationContext(); + Notification notification = new Notification(context.getApplicationInfo().icon, + "{{ args.name }}", System.currentTimeMillis()); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + notification.setLatestEventInfo(context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); + startForeground({{ service_id }}, notification); + } + + static public void start(Context ctx, String pythonServiceArgument) { + Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); + String argument = ctx.getFilesDir().getAbsolutePath(); + intent.putExtra("androidPrivate", argument); + intent.putExtra("androidArgument", argument); + intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); + intent.putExtra("pythonName", "{{ name }}"); + intent.putExtra("pythonHome", argument); + intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); + intent.putExtra("pythonServiceArgument", pythonServiceArgument); + ctx.startService(intent); + } + + static public void stop(Context ctx) { + Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); + ctx.stopService(intent); + } +} diff --git a/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java new file mode 100644 index 0000000000..df6578bdee --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java @@ -0,0 +1,59 @@ +package org.kivy.android; + +import android.util.Log; + +import java.io.IOException; +import java.net.Socket; +import java.net.InetSocketAddress; + +import android.os.SystemClock; + +import android.os.Handler; + +import org.kivy.android.PythonActivity; + +public class WebViewLoader { + private static final String TAG = "WebViewLoader"; + + public static void testConnection() { + + while (true) { + if (WebViewLoader.pingHost("localhost", {{ args.port }}, 100)) { + Log.v(TAG, "Successfully pinged localhost:{{ args.port }}"); + Handler mainHandler = new Handler(PythonActivity.mActivity.getMainLooper()); + + Runnable myRunnable = new Runnable() { + @Override + public void run() { + PythonActivity.mActivity.mWebView.loadUrl("http://127.0.0.1:{{ args.port }}/"); + Log.v(TAG, "Loaded webserver in webview"); + } + }; + mainHandler.post(myRunnable); + break; + + } else { + Log.v(TAG, "Could not ping localhost:{{ args.port }}"); + try { + Thread.sleep(100); + } catch(InterruptedException e) { + Log.v(TAG, "InterruptedException occurred when sleeping"); + } + } + } + } + + public static boolean pingHost(String host, int port, int timeout) { + Socket socket = new Socket(); + try { + socket.connect(new InetSocketAddress(host, port), timeout); + socket.close(); + return true; + } catch (IOException e) { + try {socket.close();} catch (IOException f) {return false;} + return false; // Either timeout or unreachable or failed DNS lookup. + } + } +} + + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml new file mode 100644 index 0000000000..9ab301ad94 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml new file mode 100644 index 0000000000..8b2f60c7e1 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml @@ -0,0 +1,14 @@ + + + + + + {% for dir, includes in args.extra_source_dirs %} + + {% endfor %} + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png b/pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..59a00ba6fff07cec43a4100fdf22f3f679df2349 GIT binary patch literal 16525 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 L>T(q_W}*KFob!<> literal 0 HcmV?d00001 diff --git a/pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg b/pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg new file mode 100644 index 0000000000000000000000000000000000000000..161ebc09284183771507c3eeb338cd5a7fefcb9d GIT binary patch literal 18251 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 + + {{ args.name }} + 0.1 + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml new file mode 100644 index 0000000000..9564aae306 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl b/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl new file mode 100644 index 0000000000..9564aae306 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html b/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html new file mode 100644 index 0000000000..fbbeda0617 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html @@ -0,0 +1,60 @@ + + + + + + + + Python WebView loader + + + + + + +
+
Loading...
+
+ + +
+
+ + + + + + diff --git a/pythonforandroid/bootstraps/service_only/build/whitelist.txt b/pythonforandroid/bootstraps/service_only/build/whitelist.txt new file mode 100644 index 0000000000..41b06ee258 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/whitelist.txt @@ -0,0 +1 @@ +# put files here that you need to un-blacklist From d2ccc5be330f4442c878ebe6925bd9b77c375059 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:47:43 +0200 Subject: [PATCH 02/15] added command clean_bootstraps --- pythonforandroid/toolchain.py | 9 +++++++++ 1 file changed, 9 insertions(+) mode change 100755 => 100644 pythonforandroid/toolchain.py diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py old mode 100755 new mode 100644 index 039389db44..23820a3654 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -180,6 +180,7 @@ def __init__(self): clean_all Delete all build components clean_builds Delete all build caches clean_dists Delete all compiled distributions +clean_bootstraps Delete all compiled bootstraps clean_download_cache Delete any downloaded recipe packages clean_recipe_build Delete the build files of a recipe distributions List all distributions @@ -423,6 +424,14 @@ def clean_dists(self, args): if exists(ctx.dist_dir): shutil.rmtree(ctx.dist_dir) + def clean_bootstraps(self, args): + '''Delete all the bootstrap builds.''' + for bs in Bootstrap.list_bootstraps(): + bs = Bootstrap.get_bootstrap(bs, self.ctx) + if bs.build_dir and exists(bs.build_dir): + info('Cleaning build for {} bootstrap.'.format(bs.name)) + shutil.rmtree(bs.build_dir) + def clean_builds(self, args): '''Delete all build caches for each recipe, python-install, java code and compiled libs collection. From 671737987eaf8382df70fd3dd6d23245250cf184 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:48:39 +0200 Subject: [PATCH 03/15] remove display related functionality from Hardware.java --- .../build/src/org/renpy/android/Hardware.java | 512 +++++++++--------- 1 file changed, 241 insertions(+), 271 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java index c50692d71d..c56d7b1f77 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java @@ -1,287 +1,257 @@ package org.renpy.android; +import java.util.List; + +import android.content.BroadcastReceiver; import android.content.Context; -import android.os.Vibrator; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.util.DisplayMetrics; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; -import android.view.View; - -import java.util.List; -import java.util.ArrayList; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiManager; -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; - -import org.kivy.android.PythonActivity; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Vibrator; +import android.view.View; /** - * Methods that are expected to be called via JNI, to access the - * device's non-screen hardware. (For example, the vibration and - * accelerometer.) + * Methods that are expected to be called via JNI, to access the device's + * non-screen hardware. (For example, the vibration and accelerometer.) */ public class Hardware { - // The context. - static Context context; - static View view; - - /** - * Vibrate for s seconds. - */ - public static void vibrate(double s) { - Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - if (v != null) { - v.vibrate((int) (1000 * s)); - } - } - - /** - * Get an Overview of all Hardware Sensors of an Android Device - */ - public static String getHardwareSensors() { - SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - List allSensors = sm.getSensorList(Sensor.TYPE_ALL); - - if (allSensors != null) { - String resultString = ""; - for (Sensor s : allSensors) { - resultString += String.format("Name=" + s.getName()); - resultString += String.format(",Vendor=" + s.getVendor()); - resultString += String.format(",Version=" + s.getVersion()); - resultString += String.format(",MaximumRange=" + s.getMaximumRange()); - // XXX MinDelay is not in the 2.2 - //resultString += String.format(",MinDelay=" + s.getMinDelay()); - resultString += String.format(",Power=" + s.getPower()); - resultString += String.format(",Type=" + s.getType() + "\n"); - } - return resultString; - } - return ""; - } - - - /** - * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors - */ - public static class generic3AxisSensor implements SensorEventListener { - private final SensorManager sSensorManager; - private final Sensor sSensor; - private final int sSensorType; - SensorEvent sSensorEvent; - - public generic3AxisSensor(int sensorType) { - sSensorType = sensorType; - sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - sSensor = sSensorManager.getDefaultSensor(sSensorType); - } - - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - - public void onSensorChanged(SensorEvent event) { - sSensorEvent = event; - } - - /** - * Enable or disable the Sensor by registering/unregistering - */ - public void changeStatus(boolean enable) { - if (enable) { - sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL); - } else { - sSensorManager.unregisterListener(this, sSensor); - } - } - - /** - * Read the Sensor - */ - public float[] readSensor() { - if (sSensorEvent != null) { - return sSensorEvent.values; - } else { - float rv[] = { 0f, 0f, 0f }; - return rv; - } - } - } - - public static generic3AxisSensor accelerometerSensor = null; - public static generic3AxisSensor orientationSensor = null; - public static generic3AxisSensor magneticFieldSensor = null; - - /** - * functions for backward compatibility reasons - */ - - public static void accelerometerEnable(boolean enable) { - if ( accelerometerSensor == null ) - accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER); - accelerometerSensor.changeStatus(enable); - } - public static float[] accelerometerReading() { - float rv[] = { 0f, 0f, 0f }; - if ( accelerometerSensor == null ) - return rv; - return (float[]) accelerometerSensor.readSensor(); - } - public static void orientationSensorEnable(boolean enable) { - if ( orientationSensor == null ) - orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION); - orientationSensor.changeStatus(enable); - } - public static float[] orientationSensorReading() { - float rv[] = { 0f, 0f, 0f }; - if ( orientationSensor == null ) - return rv; - return (float[]) orientationSensor.readSensor(); - } - public static void magneticFieldSensorEnable(boolean enable) { - if ( magneticFieldSensor == null ) - magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD); - magneticFieldSensor.changeStatus(enable); - } - public static float[] magneticFieldSensorReading() { - float rv[] = { 0f, 0f, 0f }; - if ( magneticFieldSensor == null ) - return rv; - return (float[]) magneticFieldSensor.readSensor(); - } - - static public DisplayMetrics metrics = new DisplayMetrics(); - - /** - * Get display DPI. - */ - public static int getDPI() { - // AND: Shouldn't have to get the metrics like this every time... - PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics); - return metrics.densityDpi; - } - - // /** - // * Show the soft keyboard. - // */ - // public static void showKeyboard(int input_type) { - // //Log.i("python", "hardware.Java show_keyword " input_type); - - // InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - - // SDLSurfaceView vw = (SDLSurfaceView) view; - - // int inputType = input_type; - - // if (vw.inputType != inputType){ - // vw.inputType = inputType; - // imm.restartInput(view); - // } - - // imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); - // } - - /** - * Hide the soft keyboard. - */ - public static void hideKeyboard() { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - /** - * Scan WiFi networks - */ - static List latestResult; - - public static void enableWifiScanner() - { - IntentFilter i = new IntentFilter(); - i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - - context.registerReceiver(new BroadcastReceiver() { - - @Override - public void onReceive(Context c, Intent i) { - // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs - WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE); - latestResult = w.getScanResults(); // Returns a of scanResults - } - - }, i); - - } - - public static String scanWifi() { - - // Now you can call this and it should execute the broadcastReceiver's - // onReceive() - WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - boolean a = wm.startScan(); - - if (latestResult != null){ - - String latestResultString = ""; - for (ScanResult result : latestResult) - { - latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level); - } - - return latestResultString; - } - - return ""; - } - - /** - * network state - */ - - public static boolean network_state = false; - - /** - * Check network state directly - * - * (only one connection can be active at a given moment, detects all network type) - * - */ - public static boolean checkNetwork() - { - boolean state = false; - final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - - final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo(); - if (activeNetwork != null && activeNetwork.isConnected()) { - state = true; - } else { - state = false; - } - - return state; - } - - /** - * To recieve network state changes - */ - public static void registerNetworkCheck() - { - IntentFilter i = new IntentFilter(); - i.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - context.registerReceiver(new BroadcastReceiver() { - - @Override - public void onReceive(Context c, Intent i) { - network_state = checkNetwork(); - } - - }, i); - } + // The context. + static Context context; + static View view; + + /** + * Vibrate for s seconds. + */ + public static void vibrate(double s) { + Vibrator v = (Vibrator) context + .getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate((int) (1000 * s)); + } + } + + /** + * Get an Overview of all Hardware Sensors of an Android Device + */ + public static String getHardwareSensors() { + SensorManager sm = (SensorManager) context + .getSystemService(Context.SENSOR_SERVICE); + List allSensors = sm.getSensorList(Sensor.TYPE_ALL); + + if (allSensors != null) { + String resultString = ""; + for (Sensor s : allSensors) { + resultString += String.format("Name=" + s.getName()); + resultString += String.format(",Vendor=" + s.getVendor()); + resultString += String.format(",Version=" + s.getVersion()); + resultString += String.format(",MaximumRange=" + + s.getMaximumRange()); + // XXX MinDelay is not in the 2.2 + // resultString += String.format(",MinDelay=" + + // s.getMinDelay()); + resultString += String.format(",Power=" + s.getPower()); + resultString += String.format(",Type=" + s.getType() + "\n"); + } + return resultString; + } + return ""; + } + + /** + * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and + * Magnetic Field Sensors + */ + public static class generic3AxisSensor implements SensorEventListener { + private final SensorManager sSensorManager; + private final Sensor sSensor; + private final int sSensorType; + SensorEvent sSensorEvent; + + public generic3AxisSensor(int sensorType) { + sSensorType = sensorType; + sSensorManager = (SensorManager) context + .getSystemService(Context.SENSOR_SERVICE); + sSensor = sSensorManager.getDefaultSensor(sSensorType); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + + public void onSensorChanged(SensorEvent event) { + sSensorEvent = event; + } + + /** + * Enable or disable the Sensor by registering/unregistering + */ + public void changeStatus(boolean enable) { + if (enable) { + sSensorManager.registerListener(this, sSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else { + sSensorManager.unregisterListener(this, sSensor); + } + } + + /** + * Read the Sensor + */ + public float[] readSensor() { + if (sSensorEvent != null) { + return sSensorEvent.values; + } else { + float rv[] = { 0f, 0f, 0f }; + return rv; + } + } + } + + public static generic3AxisSensor accelerometerSensor = null; + public static generic3AxisSensor orientationSensor = null; + public static generic3AxisSensor magneticFieldSensor = null; + + /** + * functions for backward compatibility reasons + */ + + public static void accelerometerEnable(boolean enable) { + if (accelerometerSensor == null) + accelerometerSensor = new generic3AxisSensor( + Sensor.TYPE_ACCELEROMETER); + accelerometerSensor.changeStatus(enable); + } + + public static float[] accelerometerReading() { + float rv[] = { 0f, 0f, 0f }; + if (accelerometerSensor == null) + return rv; + return (float[]) accelerometerSensor.readSensor(); + } + + public static void orientationSensorEnable(boolean enable) { + if (orientationSensor == null) + orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION); + orientationSensor.changeStatus(enable); + } + + public static float[] orientationSensorReading() { + float rv[] = { 0f, 0f, 0f }; + if (orientationSensor == null) + return rv; + return (float[]) orientationSensor.readSensor(); + } + + public static void magneticFieldSensorEnable(boolean enable) { + if (magneticFieldSensor == null) + magneticFieldSensor = new generic3AxisSensor( + Sensor.TYPE_MAGNETIC_FIELD); + magneticFieldSensor.changeStatus(enable); + } + + public static float[] magneticFieldSensorReading() { + float rv[] = { 0f, 0f, 0f }; + if (magneticFieldSensor == null) + return rv; + return (float[]) magneticFieldSensor.readSensor(); + } + + /** + * Scan WiFi networks + */ + static List latestResult; + + public static void enableWifiScanner() { + IntentFilter i = new IntentFilter(); + i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + + context.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context c, Intent i) { + // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event + // occurs + WifiManager w = (WifiManager) c + .getSystemService(Context.WIFI_SERVICE); + latestResult = w.getScanResults(); // Returns a of + // scanResults + } + + }, i); + + } + + public static String scanWifi() { + + // Now you can call this and it should execute the broadcastReceiver's + // onReceive() + WifiManager wm = (WifiManager) context + .getSystemService(Context.WIFI_SERVICE); + boolean a = wm.startScan(); + + if (latestResult != null) { + + String latestResultString = ""; + for (ScanResult result : latestResult) { + latestResultString += String.format("%s\t%s\t%d\n", + result.SSID, result.BSSID, result.level); + } + + return latestResultString; + } + + return ""; + } + + /** + * network state + */ + + public static boolean network_state = false; + + /** + * Check network state directly + * + * (only one connection can be active at a given moment, detects all network + * type) + * + */ + public static boolean checkNetwork() { + boolean state = false; + final ConnectivityManager conMgr = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + + final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo(); + if (activeNetwork != null && activeNetwork.isConnected()) { + state = true; + } else { + state = false; + } + + return state; + } + + /** + * To recieve network state changes + */ + public static void registerNetworkCheck() { + IntentFilter i = new IntentFilter(); + i.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context c, Intent i) { + network_state = checkNetwork(); + } + + }, i); + } } From a74b8d415db51fdf66ddfde813654e346b2d0755 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:48:56 +0200 Subject: [PATCH 04/15] added log exceptions to AssetExtract.java --- .../src/org/renpy/android/AssetExtract.java | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java index 52d6424e09..09e20de2b8 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java @@ -1,34 +1,28 @@ -// This string is autogenerated by ChangeAppSettings.sh, do not change -// spaces amount package org.renpy.android; -import java.io.*; - -import android.app.Activity; -import android.util.Log; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.File; - +import java.io.OutputStream; import java.util.zip.GZIPInputStream; -import android.content.res.AssetManager; +import org.kamranzafar.jtar.TarEntry; +import org.kamranzafar.jtar.TarInputStream; -import org.kamranzafar.jtar.*; +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; public class AssetExtract { private AssetManager mAssetManager = null; - private Activity mActivity = null; - public AssetExtract(Activity act) { - mActivity = act; - mAssetManager = act.getAssets(); + public AssetExtract(Context ctx) { + mAssetManager = ctx.getAssets(); } public boolean extractTar(String asset, String target) { @@ -39,8 +33,11 @@ public boolean extractTar(String asset, String target) { TarInputStream tis = null; try { - assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING); - tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192)); + assetStream = mAssetManager.open(asset, + AssetManager.ACCESS_STREAMING); + tis = new TarInputStream(new BufferedInputStream( + new GZIPInputStream(new BufferedInputStream(assetStream, + 8192)), 8192)); } catch (IOException e) { Log.e("python", "opening up extract tar", e); return false; @@ -51,12 +48,12 @@ public boolean extractTar(String asset, String target) { try { entry = tis.getNextEntry(); - } catch ( java.io.IOException e ) { + } catch (java.io.IOException e) { Log.e("python", "extracting tar", e); return false; } - if ( entry == null ) { + if (entry == null) { break; } @@ -65,8 +62,10 @@ public boolean extractTar(String asset, String target) { if (entry.isDirectory()) { try { - new File(target +"/" + entry.getName()).mkdirs(); - } catch ( SecurityException e ) { }; + new File(target + "/" + entry.getName()).mkdirs(); + } catch (SecurityException e) { + Log.e("python", "extracting tar", e); + } continue; } @@ -76,10 +75,13 @@ public boolean extractTar(String asset, String target) { try { out = new BufferedOutputStream(new FileOutputStream(path), 8192); - } catch ( FileNotFoundException e ) { - } catch ( SecurityException e ) { }; + } catch (FileNotFoundException e) { + Log.e("python", "extracting tar", e); + } catch (SecurityException e) { + Log.e("python", "extracting tar", e); + } - if ( out == null ) { + if (out == null) { Log.e("python", "could not open " + path); return false; } @@ -97,7 +99,7 @@ public boolean extractTar(String asset, String target) { out.flush(); out.close(); - } catch ( java.io.IOException e ) { + } catch (java.io.IOException e) { Log.e("python", "extracting zip", e); return false; } From c47bf596c675cef839c06a67c948d1856739ff33 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:49:40 +0200 Subject: [PATCH 05/15] write ndk dir to local.properties --- pythonforandroid/bootstraps/service_only/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py index 315092d7dd..1bade96f13 100644 --- a/pythonforandroid/bootstraps/service_only/__init__.py +++ b/pythonforandroid/bootstraps/service_only/__init__.py @@ -4,20 +4,22 @@ import glob import sh -class WebViewBootstrap(Bootstrap): - name = 'webview' +class ServiceOnlyBootstrap(Bootstrap): + name = 'service_only' - recipe_depends = ['webviewjni', ('python2', 'python3crystax')] + recipe_depends = [('python2', 'python3crystax')] def run_distribute(self): info_main('# Creating Android project from build and {} bootstrap'.format( self.name)) + info('This currently just copies the build stuff straight from the build dir.') shprint(sh.rm, '-rf', self.dist_dir) shprint(sh.cp, '-r', self.build_dir, self.dist_dir) with current_directory(self.dist_dir): with open('local.properties', 'w') as fileh: fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) + fileh.write('ndk.dir={}'.format(self.ctx.ndk_dir)) arch = self.ctx.archs[0] if len(self.ctx.archs) > 1: @@ -114,6 +116,6 @@ def run_distribute(self): self.strip_libraries(arch) self.fry_eggs(site_packages_dir) - super(WebViewBootstrap, self).run_distribute() + super(ServiceOnlyBootstrap, self).run_distribute() -bootstrap = WebViewBootstrap() +bootstrap = ServiceOnlyBootstrap() \ No newline at end of file From 2b683041907b177a884ac079dfb9fbb357a2c6f9 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:50:03 +0200 Subject: [PATCH 06/15] strip PythonActivity --- .../service_only/build/jni/src/start.c | 38 -- .../src/org/kivy/android/PythonActivity.java | 393 ------------------ .../src/org/kivy/android/PythonService.java | 156 +++++-- 3 files changed, 116 insertions(+), 471 deletions(-) delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/start.c b/pythonforandroid/bootstraps/service_only/build/jni/src/start.c index 34372d2ee2..37a9b1d90f 100644 --- a/pythonforandroid/bootstraps/service_only/build/jni/src/start.c +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/start.c @@ -314,42 +314,4 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart( main(1, argv); } -void Java_org_kivy_android_PythonActivity_nativeSetEnv( - JNIEnv* env, jclass jcls, - jstring j_name, jstring j_value) -/* JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv( */ -/* JNIEnv* env, jclass jcls, */ -/* jstring j_name, jstring j_value) */ -{ - jboolean iscopy; - const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy); - const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy); - setenv(name, value, 1); - (*env)->ReleaseStringUTFChars(env, j_name, name); - (*env)->ReleaseStringUTFChars(env, j_value, value); -} - - -void Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) -{ - /* This nativeInit follows SDL2 */ - - /* This interface could expand with ABI negotiation, calbacks, etc. */ - /* SDL_Android_Init(env, cls); */ - - /* SDL_SetMainReady(); */ - - /* Run the application code! */ - int status; - char *argv[2]; - argv[0] = "Python_app"; - argv[1] = NULL; - /* status = SDL_main(1, argv); */ - - main(1, argv); - - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ -} - #endif diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java deleted file mode 100644 index ba00ab36f2..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonActivity.java +++ /dev/null @@ -1,393 +0,0 @@ - -package org.kivy.android; - -import java.net.Socket; -import java.net.InetSocketAddress; - -import android.os.SystemClock; - -import java.io.InputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.ArrayList; - -import android.app.*; -import android.content.*; -import android.view.*; -import android.view.ViewGroup; -import android.view.SurfaceView; -import android.app.Activity; -import android.content.Intent; -import android.util.Log; -import android.widget.Toast; -import android.os.Bundle; -import android.os.PowerManager; -import android.graphics.PixelFormat; -import android.view.SurfaceHolder; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.ApplicationInfo; -import android.content.Intent; -import android.widget.ImageView; -import java.io.InputStream; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import android.widget.AbsoluteLayout; -import android.view.ViewGroup.LayoutParams; - -import android.webkit.WebViewClient; -import android.webkit.WebView; - -import org.kivy.android.PythonUtil; - -import org.kivy.android.WebViewLoader; - -import org.renpy.android.ResourceManager; -import org.renpy.android.AssetExtract; - -public class PythonActivity extends Activity { - // This activity is modified from a mixture of the SDLActivity and - // PythonActivity in the SDL2 bootstrap, but removing all the SDL2 - // specifics. - - private static final String TAG = "PythonActivity"; - - public static PythonActivity mActivity = null; - - /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ - public static boolean mBrokenLibraries; - - protected static ViewGroup mLayout; - protected static WebView mWebView; - - protected static Thread mPythonThread; - - private ResourceManager resourceManager = null; - private Bundle mMetaData = null; - private PowerManager.WakeLock mWakeLock = null; - - public static void initialize() { - // The static nature of the singleton and Android quirkyness force us to initialize everything here - // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values - mWebView = null; - mLayout = null; - mBrokenLibraries = false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.v(TAG, "My oncreate running"); - resourceManager = new ResourceManager(this); - - Log.v(TAG, "Ready to unpack"); - unpackData("private", getFilesDir()); - - this.mActivity = this; - - Log.v("Python", "Device: " + android.os.Build.DEVICE); - Log.v("Python", "Model: " + android.os.Build.MODEL); - super.onCreate(savedInstanceState); - - PythonActivity.initialize(); - - // Load shared libraries - String errorMsgBrokenLib = ""; - try { - loadLibraries(); - } catch(UnsatisfiedLinkError e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } catch(Exception e) { - System.err.println(e.getMessage()); - mBrokenLibraries = true; - errorMsgBrokenLib = e.getMessage(); - } - - if (mBrokenLibraries) - { - AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); - dlgAlert.setMessage("An error occurred while trying to load the application libraries. Please try again and/or reinstall." - + System.getProperty("line.separator") - + System.getProperty("line.separator") - + "Error: " + errorMsgBrokenLib); - dlgAlert.setTitle("Python Error"); - dlgAlert.setPositiveButton("Exit", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog,int id) { - // if this button is clicked, close current activity - PythonActivity.mActivity.finish(); - } - }); - dlgAlert.setCancelable(false); - dlgAlert.create().show(); - - return; - } - - // Set up the webview - mWebView = new WebView(this); - mWebView.getSettings().setJavaScriptEnabled(true); - mWebView.getSettings().setDomStorageEnabled(true); - mWebView.loadUrl("file:///" + mActivity.getFilesDir().getAbsolutePath() + "/_load.html"); - - mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); - mWebView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - view.loadUrl(url); - return false; - } - }); - - mLayout = new AbsoluteLayout(this); - mLayout.addView(mWebView); - - setContentView(mLayout); - - String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); - Log.v(TAG, "Setting env vars for start.c and Python to use"); - PythonActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); - PythonActivity.nativeSetEnv("ANDROID_ARGUMENT", mFilesDirectory); - PythonActivity.nativeSetEnv("ANDROID_APP_PATH", mFilesDirectory); - PythonActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo"); - PythonActivity.nativeSetEnv("PYTHONHOME", mFilesDirectory); - PythonActivity.nativeSetEnv("PYTHONPATH", mFilesDirectory + ":" + mFilesDirectory + "/lib"); - - try { - Log.v(TAG, "Access to our meta-data..."); - this.mMetaData = this.mActivity.getPackageManager().getApplicationInfo( - this.mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; - - PowerManager pm = (PowerManager) this.mActivity.getSystemService(Context.POWER_SERVICE); - if ( this.mMetaData.getInt("wakelock") == 1 ) { - this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); - } - } catch (PackageManager.NameNotFoundException e) { - } - - final Thread pythonThread = new Thread(new PythonMain(), "PythonThread"); - PythonActivity.mPythonThread = pythonThread; - pythonThread.start(); - - final Thread wvThread = new Thread(new WebViewLoaderMain(), "WvThread"); - wvThread.start(); - - } - - public void loadLibraries() { - PythonUtil.loadLibraries(getFilesDir()); - } - - public void recursiveDelete(File f) { - if (f.isDirectory()) { - for (File r : f.listFiles()) { - recursiveDelete(r); - } - } - f.delete(); - } - - /** - * Show an error using a toast. (Only makes sense from non-UI - * threads.) - */ - public void toastError(final String msg) { - - final Activity thisActivity = this; - - runOnUiThread(new Runnable () { - public void run() { - Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show(); - } - }); - - // Wait to show the error. - synchronized (this) { - try { - this.wait(1000); - } catch (InterruptedException e) { - } - } - } - - public void unpackData(final String resource, File target) { - - Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); - - // The version of data in memory and on disk. - String data_version = resourceManager.getString(resource + "_version"); - String disk_version = null; - - Log.v(TAG, "Data version is " + data_version); - - // If no version, no unpacking is necessary. - if (data_version == null) { - return; - } - - // Check the current disk version, if any. - String filesDir = target.getAbsolutePath(); - String disk_version_fn = filesDir + "/" + resource + ".version"; - - try { - byte buf[] = new byte[64]; - InputStream is = new FileInputStream(disk_version_fn); - int len = is.read(buf); - disk_version = new String(buf, 0, len); - is.close(); - } catch (Exception e) { - disk_version = ""; - } - - // If the disk data is out of date, extract it and write the - // version file. - // if (! data_version.equals(disk_version)) { - if (! data_version.equals(disk_version)) { - Log.v(TAG, "Extracting " + resource + " assets."); - - recursiveDelete(target); - target.mkdirs(); - - AssetExtract ae = new AssetExtract(this); - if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { - toastError("Could not extract " + resource + " data."); - } - - try { - // Write .nomedia. - new File(target, ".nomedia").createNewFile(); - - // Write version file. - FileOutputStream os = new FileOutputStream(disk_version_fn); - os.write(data_version.getBytes()); - os.close(); - } catch (Exception e) { - Log.w("python", e); - } - } - } - - public static ViewGroup getLayout() { - return mLayout; - } - - - //---------------------------------------------------------------------------- - // Listener interface for onNewIntent - // - - public interface NewIntentListener { - void onNewIntent(Intent intent); - } - - private List newIntentListeners = null; - - public void registerNewIntentListener(NewIntentListener listener) { - if ( this.newIntentListeners == null ) - this.newIntentListeners = Collections.synchronizedList(new ArrayList()); - this.newIntentListeners.add(listener); - } - - public void unregisterNewIntentListener(NewIntentListener listener) { - if ( this.newIntentListeners == null ) - return; - this.newIntentListeners.remove(listener); - } - - @Override - protected void onNewIntent(Intent intent) { - if ( this.newIntentListeners == null ) - return; - this.onResume(); - synchronized ( this.newIntentListeners ) { - Iterator iterator = this.newIntentListeners.iterator(); - while ( iterator.hasNext() ) { - (iterator.next()).onNewIntent(intent); - } - } - } - - //---------------------------------------------------------------------------- - // Listener interface for onActivityResult - // - - public interface ActivityResultListener { - void onActivityResult(int requestCode, int resultCode, Intent data); - } - - private List activityResultListeners = null; - - public void registerActivityResultListener(ActivityResultListener listener) { - if ( this.activityResultListeners == null ) - this.activityResultListeners = Collections.synchronizedList(new ArrayList()); - this.activityResultListeners.add(listener); - } - - public void unregisterActivityResultListener(ActivityResultListener listener) { - if ( this.activityResultListeners == null ) - return; - this.activityResultListeners.remove(listener); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - if ( this.activityResultListeners == null ) - return; - this.onResume(); - synchronized ( this.activityResultListeners ) { - Iterator iterator = this.activityResultListeners.iterator(); - while ( iterator.hasNext() ) - (iterator.next()).onActivityResult(requestCode, resultCode, intent); - } - } - - public static void start_service(String serviceTitle, String serviceDescription, - String pythonServiceArgument) { - Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); - String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath(); - String filesDirectory = argument; - serviceIntent.putExtra("androidPrivate", argument); - serviceIntent.putExtra("androidArgument", argument); - serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo"); - serviceIntent.putExtra("pythonHome", argument); - serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/lib"); - serviceIntent.putExtra("serviceTitle", serviceTitle); - serviceIntent.putExtra("serviceDescription", serviceDescription); - serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument); - PythonActivity.mActivity.startService(serviceIntent); - } - - public static void stop_service() { - Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); - PythonActivity.mActivity.stopService(serviceIntent); - } - - - public static native void nativeSetEnv(String j_name, String j_value); - public static native int nativeInit(Object arguments); - -} - - -class PythonMain implements Runnable { - @Override - public void run() { - PythonActivity.nativeInit(new String[0]); - } -} - -class WebViewLoaderMain implements Runnable { - @Override - public void run() { - WebViewLoader.testConnection(); - } -} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java index f8dde3e0d2..8660bffe86 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java @@ -1,21 +1,22 @@ package org.kivy.android; import android.app.Service; -import android.os.IBinder; -import android.os.Bundle; import android.content.Intent; -import android.content.Context; -import android.util.Log; -import android.app.Notification; -import android.app.PendingIntent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.IBinder; import android.os.Process; +import android.util.Log; -import org.kivy.android.PythonUtil; - -import org.renpy.android.Hardware; +import org.renpy.android.AssetExtract; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; public class PythonService extends Service implements Runnable { + private static String TAG = "PythonService"; // Thread for Python code private Thread pythonThread = null; @@ -27,6 +28,7 @@ public class PythonService extends Service implements Runnable { private String pythonHome; private String pythonPath; private String serviceEntrypoint; + // Argument to pass to Python code, private String pythonServiceArgument; public static PythonService mService = null; @@ -53,17 +55,20 @@ public IBinder onBind(Intent arg0) { @Override public void onCreate() { + Log.v(TAG, "Device: " + android.os.Build.DEVICE); + Log.v(TAG, "Model: " + android.os.Build.MODEL); + unpackData("private", getFilesDir()); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (pythonThread != null) { - Log.v("python service", "service exists, do not start again"); + Log.v(TAG, "Service exists, do not start again"); return START_NOT_STICKY; } - startIntent = intent; + startIntent = intent; Bundle extras = intent.getExtras(); androidPrivate = extras.getString("androidPrivate"); androidArgument = extras.getString("androidArgument"); @@ -76,54 +81,125 @@ public int onStartCommand(Intent intent, int flags, int startId) { pythonThread = new Thread(this); pythonThread.start(); - if (canDisplayNotification()) { - doStartForeground(extras); - } + if (canDisplayNotification()) { + doStartForeground(extras); + } return startType(); } - protected void doStartForeground(Bundle extras) { - String serviceTitle = extras.getString("serviceTitle"); - String serviceDescription = extras.getString("serviceDescription"); - - Context context = getApplicationContext(); - Notification notification = new Notification(context.getApplicationInfo().icon, - serviceTitle, System.currentTimeMillis()); - Intent contextIntent = new Intent(context, PythonActivity.class); - PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(context, serviceTitle, serviceDescription, pIntent); - startForeground(1, notification); - } + protected void doStartForeground(Bundle extras) { + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + Context context = getApplicationContext(); + Notification notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, + System.currentTimeMillis()); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, + contextIntent, PendingIntent.FLAG_UPDATE_CURRENT); + notification.setLatestEventInfo(context, serviceTitle, + serviceDescription, pIntent); + startForeground(1, notification); + } @Override public void onDestroy() { super.onDestroy(); pythonThread = null; if (autoRestartService && startIntent != null) { - Log.v("python service", "service restart requested"); + Log.v(TAG, "Service restart requested"); startService(startIntent); } Process.killProcess(Process.myPid()); } @Override - public void run(){ + public void run() { PythonUtil.loadLibraries(getFilesDir()); - this.mService = this; - nativeStart( - androidPrivate, androidArgument, - serviceEntrypoint, pythonName, - pythonHome, pythonPath, - pythonServiceArgument); + mService = this; + nativeStart(androidPrivate, androidArgument, serviceEntrypoint, + pythonName, pythonHome, pythonPath, pythonServiceArgument); stopSelf(); } + public void recursiveDelete(File f) { + if (f.isDirectory()) { + for (File r : f.listFiles()) { + recursiveDelete(r); + } + } + f.delete(); + } + + public void unpackData(final String resource, File target) { + + Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); + + // The version of data in memory and on disk. + String data_version = null; + String disk_version = null; + + try { + PackageManager manager = this.getPackageManager(); + PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0); + data_version = info.versionName; + + Log.v(TAG, "Data version is " + data_version); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Data version not found of " + resource + " data."); + } + + // If no version, no unpacking is necessary. + if (data_version == null) { + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String disk_version_fn = filesDir + "/" + resource + ".version"; + + try { + byte buf[] = new byte[64]; + FileInputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + disk_version = ""; + } + + // If the disk data is out of date, extract it and write the version + // file. + if (!data_version.equals(disk_version)) { + Log.v(TAG, "Extracting " + resource + " assets."); + + recursiveDelete(target); + target.mkdirs(); + + AssetExtract ae = new AssetExtract(this); + if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { + Log.e(TAG, "Could not extract " + resource + " data."); + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(disk_version_fn); + os.write(data_version.getBytes()); + os.close(); + } catch (Exception e) { + Log.w("python", e); + } + } + } + // Native part - public static native void nativeStart( - String androidPrivate, String androidArgument, - String serviceEntrypoint, String pythonName, - String pythonHome, String pythonPath, - String pythonServiceArgument); + public static native void nativeStart(String androidPrivate, + String androidArgument, String serviceEntrypoint, + String pythonName, String pythonHome, String pythonPath, + String pythonServiceArgument); } From 809d3498ba55fc24fe42d34c0a494409b62c6f63 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:50:24 +0200 Subject: [PATCH 07/15] strip AndroidManifest.tmpl.xml --- .../build/templates/AndroidManifest.tmpl.xml | 89 +------------------ 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml index 079638e0e9..0ac9581be1 100644 --- a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml @@ -1,74 +1,3 @@ - - - - = 9 %} - android:xlargeScreens="true" - {% endif %} - /> - - - - - - - {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} - {% endfor %} - - {% if args.wakelock %} - - {% endif %} - - {% if args.billing_pubkey %} - - {% endif %} - - - - - {% for m in args.meta_data %} - {% endfor %} - - - - - - - - {%- if args.intent_filters -%} - {{- args.intent_filters -}} - {%- endif -%} - - {% if service %} @@ -76,20 +5,4 @@ {% for name in service_names %} - {% endfor %} - - {% if args.billing_pubkey %} - - - - - - - - - {% endif %} - - - + {% endfor %} \ No newline at end of file From 2f2a704c3820be07a4b507310f08045f42a38019 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:51:08 +0200 Subject: [PATCH 08/15] read .p4a configuration file --- .../bootstraps/service_only/build/build.py | 174 ++++++++++-------- 1 file changed, 95 insertions(+), 79 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/build.py b/pythonforandroid/bootstraps/service_only/build/build.py index 20101863c1..688d49bc82 100755 --- a/pythonforandroid/bootstraps/service_only/build/build.py +++ b/pythonforandroid/bootstraps/service_only/build/build.py @@ -12,6 +12,7 @@ from zipfile import ZipFile import sys import re +import shlex from fnmatch import fnmatch @@ -236,7 +237,6 @@ def make_package(args): tar_dirs.append('private') if exists('crystax_python'): tar_dirs.append('crystax_python') - tar_dirs.append('webview_includes') if args.private: make_tar('assets/private.mp3', tar_dirs, args.ignore_path) # else: @@ -258,12 +258,12 @@ def make_package(args): # Prepare some variables for templating process - default_icon = 'templates/kivy-icon.png' - shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') +# default_icon = 'templates/kivy-icon.png' +# shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') - default_presplash = 'templates/kivy-presplash.jpg' - shutil.copy(args.presplash or default_presplash, - 'res/drawable/presplash.jpg') +# default_presplash = 'templates/kivy-presplash.jpg' +# shutil.copy(args.presplash or default_presplash, +# 'res/drawable/presplash.jpg') # If extra Java jars were requested, copy them into the libs directory if args.add_jar: @@ -273,19 +273,19 @@ def make_package(args): sys.exit(-1) shutil.copy(jarname, 'libs') - versioned_name = (args.name.replace(' ', '').replace('\'', '') + - '-' + args.version) +# versioned_name = (args.name.replace(' ', '').replace('\'', '') + +# '-' + args.version) - version_code = 0 - if not args.numeric_version: - for i in args.version.split('.'): - version_code *= 100 - version_code += int(i) - args.numeric_version = str(version_code) +# version_code = 0 +# if not args.numeric_version: +# for i in args.version.split('.'): +# version_code *= 100 +# version_code += int(i) +# args.numeric_version = str(version_code) - if args.intent_filters: - with open(args.intent_filters) as fd: - args.intent_filters = fd.read() +# if args.intent_filters: +# with open(args.intent_filters) as fd: +# args.intent_filters = fd.read() if args.extra_source_dirs: esd = [] @@ -336,37 +336,40 @@ def make_package(args): ) render( - 'build.tmpl.xml', - 'build.xml', - args=args, - versioned_name=versioned_name) + 'app.build.tmpl.gradle', + 'app.build.gradle', + args=args + ) - render( - 'strings.tmpl.xml', - 'res/values/strings.xml', - args=args) +# render( +# 'build.tmpl.xml', +# 'build.xml', +# args=args, +# versioned_name=versioned_name) - render( - 'custom_rules.tmpl.xml', - 'custom_rules.xml', - args=args) +# render( +# 'strings.tmpl.xml', +# 'res/values/strings.xml', +# args=args) - render('WebViewLoader.tmpl.java', - 'src/org/kivy/android/WebViewLoader.java', - args=args) +# render( +# 'custom_rules.tmpl.xml', +# 'custom_rules.xml', +# args=args) - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml')) as fileh: - lines = fileh.read() +# with open(join(dirname(__file__), 'res', +# 'values', 'strings.xml')) as fileh: +# lines = fileh.read() - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml'), 'w') as fileh: - fileh.write(re.sub(r'"private_version">[0-9\.]*<', - '"private_version">{}<'.format( - str(time.time())), lines)) +# with open(join(dirname(__file__), 'res', +# 'values', 'strings.xml'), 'w') as fileh: +# fileh.write(re.sub(r'"private_version">[0-9\.]*<', +# '"private_version">{}<'.format( +# str(time.time())), lines)) def parse_args(args=None): + global BLACKLIST_PATTERNS, WHITELIST_PATTERNS default_android_api = 12 import argparse @@ -384,32 +387,32 @@ def parse_args(args=None): help=('The name of the java package the project will be' ' packaged under.'), required=True) - ap.add_argument('--name', dest='name', - help=('The human-readable name of the project.'), - required=True) - ap.add_argument('--numeric-version', dest='numeric_version', - help=('The numeric version number of the project. If not ' - 'given, this is automatically computed from the ' - 'version.')) - ap.add_argument('--version', dest='version', - help=('The version number of the project. This should ' - 'consist of numbers and dots, and should have the ' - 'same number of groups of numbers as previous ' - 'versions.'), - required=True) - ap.add_argument('--orientation', dest='orientation', default='portrait', - help=('The orientation that the game will display in. ' - 'Usually one of "landscape", "portrait" or ' - '"sensor"')) - ap.add_argument('--icon', dest='icon', - help='A png file to use as the icon for the application.') - ap.add_argument('--permission', dest='permissions', action='append', - help='The permissions to give this app.') +# ap.add_argument('--name', dest='name', +# help=('The human-readable name of the project.'), +# required=True) +# ap.add_argument('--numeric-version', dest='numeric_version', +# help=('The numeric version number of the project. If not ' +# 'given, this is automatically computed from the ' +# 'version.')) +# ap.add_argument('--version', dest='version', +# help=('The version number of the project. This should ' +# 'consist of numbers and dots, and should have the ' +# 'same number of groups of numbers as previous ' +# 'versions.'), +# required=True) +# ap.add_argument('--orientation', dest='orientation', default='portrait', +# help=('The orientation that the game will display in. ' +# 'Usually one of "landscape", "portrait" or ' +# '"sensor"')) +# ap.add_argument('--icon', dest='icon', +# help='A png file to use as the icon for the application.') +# ap.add_argument('--permission', dest='permissions', action='append', +# help='The permissions to give this app.') ap.add_argument('--meta-data', dest='meta_data', action='append', help='Custom key=value to add in application metadata') - ap.add_argument('--presplash', dest='presplash', - help=('A jpeg file to use as a screen while the ' - 'application is loading.')) +# ap.add_argument('--presplash', dest='presplash', +# help=('A jpeg file to use as a screen while the ' +# 'application is loading.')) ap.add_argument('--wakelock', dest='wakelock', action='store_true', help=('Indicate if the application needs the device ' 'to stay on')) @@ -435,36 +438,49 @@ def parse_args(args=None): help=('Minimum Android SDK version to use. Default to ' 'the value of ANDROIDAPI, or {} if not set' .format(default_android_api))) - ap.add_argument('--intent-filters', dest='intent_filters', - help=('Add intent-filters xml rules to the ' - 'AndroidManifest.xml file. The argument is a ' - 'filename containing xml. The filename should be ' - 'located relative to the python-for-android ' - 'directory')) - ap.add_argument('--with-billing', dest='billing_pubkey', - help='If set, the billing service will be added (not implemented)') +# ap.add_argument('--intent-filters', dest='intent_filters', +# help=('Add intent-filters xml rules to the ' +# 'AndroidManifest.xml file. The argument is a ' +# 'filename containing xml. The filename should be ' +# 'located relative to the python-for-android ' +# 'directory')) +# ap.add_argument('--with-billing', dest='billing_pubkey', +# help='If set, the billing service will be added (not implemented)') ap.add_argument('--service', dest='services', action='append', help='Declare a new service entrypoint: ' 'NAME:PATH_TO_PY[:foreground]') ap.add_argument('--add-source', dest='extra_source_dirs', action='append', help='Include additional source dirs in Java build') - ap.add_argument('--port', help='The port on localhost that the WebView will access', - default='5000') + + def _read_configuration(): + # search for a .p4a configuration file in the current directory + if not exists(".p4a"): + return + print("Reading .p4a configuration") + with open(".p4a") as fd: + lines = fd.readlines() + lines = [shlex.split(line) + for line in lines if not line.startswith("#")] + for line in lines: + for arg in line: + sys.argv.append(arg) + + _read_configuration() if args is None: args = sys.argv[1:] args = ap.parse_args(args) args.ignore_path = [] - if args.billing_pubkey: - print('Billing not yet supported in sdl2 bootstrap!') - exit(1) +# if args.billing_pubkey: +# print('Billing not yet supported in sdl2 bootstrap!') +# exit(1) if args.sdk_version == -1: args.sdk_version = args.min_sdk_version - if args.permissions is None: - args.permissions = [] +# if args.permissions is None: +# args.permissions = [] if args.meta_data is None: args.meta_data = [] From 6a7ecd210900a51228518a49d476ab2b9e7d3ae8 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:51:24 +0200 Subject: [PATCH 09/15] remove sdl2 references --- .../service_only/build/jni/src/Android.mk | 2 - .../build/jni/src/Android_static.mk | 2 - .../src/org/kivy/android/PythonUtil.java | 72 +++++++++---------- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk index b431059f12..018a7cadf0 100644 --- a/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk @@ -4,8 +4,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := main -# LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include - # Add your application source files here... LOCAL_SRC_FILES := start.c pyjniusjni.c diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk b/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk index faed669c0e..2de278ee00 100644 --- a/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk +++ b/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk @@ -6,7 +6,5 @@ LOCAL_MODULE := main LOCAL_SRC_FILES := YourSourceHere.c -LOCAL_STATIC_LIBRARIES := SDL2_static - include $(BUILD_SHARED_LIBRARY) $(call import-module,SDL)LOCAL_PATH := $(call my-dir) diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java index 9d532b613f..75d06e6582 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java @@ -4,53 +4,45 @@ import android.util.Log; - public class PythonUtil { - private static final String TAG = "PythonUtil"; protected static String[] getLibraries() { - return new String[] { - // "SDL2", - // "SDL2_image", - // "SDL2_mixer", - // "SDL2_ttf", - "python2.7", - "python3.5m", - "main" - }; - } + return new String[] { "python2.7", "python3.5m", "main" }; + } public static void loadLibraries(File filesDir) { - String filesDirPath = filesDir.getAbsolutePath(); - boolean skippedPython = false; + String filesDirPath = filesDir.getAbsolutePath(); + boolean skippedPython = false; for (String lib : getLibraries()) { - try { - System.loadLibrary(lib); - } catch(UnsatisfiedLinkError e) { - if (lib.startsWith("python") && !skippedPython) { - skippedPython = true; - continue; - } - throw e; - } - } - - try { - System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so"); - System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so"); - } catch(UnsatisfiedLinkError e) { - Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay."); - } - - try { - // System.loadLibrary("ctypes"); - System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so"); - } catch(UnsatisfiedLinkError e) { - Log.v(TAG, "Unsatisfied linker when loading ctypes"); - } - - Log.v(TAG, "Loaded everything!"); + try { + System.loadLibrary(lib); + } catch (UnsatisfiedLinkError e) { + if (lib.startsWith("python") && !skippedPython) { + skippedPython = true; + continue; + } + throw e; + } + } + + try { + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so"); + System.load(filesDirPath + + "/lib/python2.7/lib-dynload/unicodedata.so"); + } catch (UnsatisfiedLinkError e) { + Log.v("PythonUtil", + "Failed to load _io.so or unicodedata.so...but that's okay."); + } + + try { + // System.loadLibrary("ctypes"); + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so"); + } catch (UnsatisfiedLinkError e) { + Log.v("PythonUtil", "Unsatisfied linker when loading ctypes"); + } + + Log.v("PythonUtil", "Loaded everything!"); } } From e184c8beb722ed457c087943bc73553e0885c0a4 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 19:51:36 +0200 Subject: [PATCH 10/15] .gradle build template files --- .../service_only/build/build.gradle | 21 +++++++ .../service_only/build/gradle.properties | 21 +++++++ .../build/templates/app.build.tmpl.gradle | 59 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 pythonforandroid/bootstraps/service_only/build/build.gradle create mode 100644 pythonforandroid/bootstraps/service_only/build/gradle.properties create mode 100644 pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle diff --git a/pythonforandroid/bootstraps/service_only/build/build.gradle b/pythonforandroid/bootstraps/service_only/build/build.gradle new file mode 100644 index 0000000000..f56187ae3f --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/build.gradle @@ -0,0 +1,21 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +allprojects { + repositories { + jcenter() + } +} +buildscript { + repositories { + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle-experimental:0.7.0" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/pythonforandroid/bootstraps/service_only/build/gradle.properties b/pythonforandroid/bootstraps/service_only/build/gradle.properties new file mode 100644 index 0000000000..24a89fbbb1 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useDeprecatedNdk=true +org.gradle.jvmargs=-Xmx4096M \ No newline at end of file diff --git a/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle b/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle new file mode 100644 index 0000000000..514ba0f816 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle @@ -0,0 +1,59 @@ +apply plugin: 'com.android.model.application' + +model { + android { + compileSdkVersion {{ args.sdk_version }} + buildToolsVersion "23.0.3" + + defaultConfig { + applicationId "{{ args.package }}" + minSdkVersion.apiLevel {{ args.min_sdk_version }} + targetSdkVersion.apiLevel {{ args.sdk_version }} + versionCode {{ args.numeric_version }} + versionName "{{ args.version }}" + + buildConfigFields { + create() { + type "int" + name "VALUE" + value "1" + } + } + } + ndk { + abiFilters.add("armeabi-v7a") + moduleName = "main" + toolchain = "gcc" + toolchainVersion = "4.9" + platformVersion = 16 + stl = "gnustl_shared" + renderscriptNdkMode = false + CFlags.add("-I" + file("src/main/jni/include/python2.7")) + ldFlags.add("-L" + file("src/main/jni/lib")) + ldLibs.addAll(["log", "python2.7"]) + } + // Configures source set directory. + sources { + main { + jniLibs { + dependencies { + library "gnustl_shared" + // add pre-built libraries here and locate them below: + } + } + } + } + } + repositories { + libs(PrebuiltLibraries) { + gnustl_shared { + binaries.withType(SharedLibraryBinary) { + sharedLibraryFile = file("src/main/jniLibs/${targetPlatform.getName()}/libgnustl_shared.so") + } + } + // more here + } + } +} + +// normal project dependencies here \ No newline at end of file From 7b16332158c1cdfeee9218d3ec371b4478b2eb64 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Sat, 28 May 2016 20:01:44 +0200 Subject: [PATCH 11/15] remove unused build files --- .../service_only/build/AndroidManifest.xml | 45 --------- .../service_only/build/ant.properties | 18 ---- .../service_only/build/build.properties | 21 ---- .../bootstraps/service_only/build/build.xml | 93 ----------------- .../service_only/build/proguard-project.txt | 20 ---- .../service_only/build/res/drawable/.gitkeep | 0 .../service_only/build/res/drawable/icon.png | Bin 16525 -> 0 bytes .../service_only/build/res/layout/main.xml | 13 --- .../service_only/build/res/values/strings.xml | 5 - .../android/GenericBroadcastReceiver.java | 19 ---- .../GenericBroadcastReceiverCallback.java | 8 -- .../src/org/renpy/android/PythonActivity.java | 12 --- .../src/org/renpy/android/PythonService.java | 12 --- .../org/renpy/android/ResourceManager.java | 54 ---------- .../build/templates/WebViewLoader.tmpl.java | 59 ----------- .../build/templates/build.tmpl.xml | 95 ------------------ .../build/templates/custom_rules.tmpl.xml | 14 --- .../build/templates/kivy-icon.png | Bin 16525 -> 0 bytes .../build/templates/kivy-presplash.jpg | Bin 18251 -> 0 bytes .../build/templates/strings.tmpl.xml | 5 - .../build/templates/test/build.tmpl.xml | 93 ----------------- .../build/templates/test/build.xml.tmpl | 93 ----------------- .../build/webview_includes/_load.html | 60 ----------- 23 files changed, 739 deletions(-) delete mode 100644 pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/ant.properties delete mode 100644 pythonforandroid/bootstraps/service_only/build/build.properties delete mode 100644 pythonforandroid/bootstraps/service_only/build/build.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/proguard-project.txt delete mode 100644 pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep delete mode 100644 pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png delete mode 100644 pythonforandroid/bootstraps/service_only/build/res/layout/main.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/res/values/strings.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml delete mode 100644 pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl delete mode 100644 pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html diff --git a/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml b/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml deleted file mode 100644 index a3dfc7b224..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/ant.properties b/pythonforandroid/bootstraps/service_only/build/ant.properties deleted file mode 100644 index f74e644b8a..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/ant.properties +++ /dev/null @@ -1,18 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -source.absolute.dir = tmp-src diff --git a/pythonforandroid/bootstraps/service_only/build/build.properties b/pythonforandroid/bootstraps/service_only/build/build.properties deleted file mode 100644 index f12e258691..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/build.properties +++ /dev/null @@ -1,21 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -key.store=${env.P4A_RELEASE_KEYSTORE} -key.alias=${env.P4A_RELEASE_KEYALIAS} -key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD} -key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD} diff --git a/pythonforandroid/bootstraps/service_only/build/build.xml b/pythonforandroid/bootstraps/service_only/build/build.xml deleted file mode 100644 index 9f19a077b1..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/build.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/proguard-project.txt b/pythonforandroid/bootstraps/service_only/build/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep b/pythonforandroid/bootstraps/service_only/build/res/drawable/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png b/pythonforandroid/bootstraps/service_only/build/res/drawable/icon.png deleted file mode 100644 index 59a00ba6fff07cec43a4100fdf22f3f679df2349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16525 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 L>T(q_W}*KFob!<> diff --git a/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml b/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml deleted file mode 100644 index 123c4b6eac..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/res/layout/main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml b/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml deleted file mode 100644 index daebceb9d5..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - SDL App - 0.1 - diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java deleted file mode 100644 index 58a1c5edf8..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiver.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.kivy.android; - -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.Context; - -public class GenericBroadcastReceiver extends BroadcastReceiver { - - GenericBroadcastReceiverCallback listener; - - public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) { - super(); - this.listener = listener; - } - - public void onReceive(Context context, Intent intent) { - this.listener.onReceive(context, intent); - } -} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java deleted file mode 100644 index 1a87c98b2d..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.kivy.android; - -import android.content.Intent; -import android.content.Context; - -public interface GenericBroadcastReceiverCallback { - void onReceive(Context context, Intent intent); -}; diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java deleted file mode 100644 index 0d34d31c9a..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonActivity.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.renpy.android; - -import android.util.Log; - -class PythonActivity extends org.kivy.android.PythonActivity { - static { - Log.w("PythonActivity", "Accessing org.renpy.android.PythonActivity " - + "is deprecated and will be removed in a " - + "future version. Please switch to " - + "org.kivy.android.PythonActivity."); - } -} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java deleted file mode 100644 index 73febed68a..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/PythonService.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.renpy.android; - -import android.util.Log; - -class PythonService extends org.kivy.android.PythonService { - static { - Log.w("PythonService", "Accessing org.renpy.android.PythonService " - + "is deprecated and will be removed in a " - + "future version. Please switch to " - + "org.kivy.android.PythonService."); - } -} diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java deleted file mode 100644 index 47455abb04..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/ResourceManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * This class takes care of managing resources for us. In our code, we - * can't use R, since the name of the package containing R will - * change. (This same code is used in both org.renpy.android and - * org.renpy.pygame.) So this is the next best thing. - */ - -package org.renpy.android; - -import android.app.Activity; -import android.content.res.Resources; -import android.view.View; - -import android.util.Log; - -public class ResourceManager { - - private Activity act; - private Resources res; - - public ResourceManager(Activity activity) { - act = activity; - res = act.getResources(); - } - - public int getIdentifier(String name, String kind) { - Log.v("SDL", "getting identifier"); - Log.v("SDL", "kind is " + kind + " and name " + name); - Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName())); - return res.getIdentifier(name, kind, act.getPackageName()); - } - - public String getString(String name) { - - try { - Log.v("SDL", "asked to get string " + name); - return res.getString(getIdentifier(name, "string")); - } catch (Exception e) { - Log.v("SDL", "got exception looking for string!"); - return null; - } - } - - public View inflateView(String name) { - int id = getIdentifier(name, "layout"); - return act.getLayoutInflater().inflate(id, null); - } - - public View getViewById(View v, String name) { - int id = getIdentifier(name, "id"); - return v.findViewById(id); - } - -} diff --git a/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java deleted file mode 100644 index df6578bdee..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/templates/WebViewLoader.tmpl.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.kivy.android; - -import android.util.Log; - -import java.io.IOException; -import java.net.Socket; -import java.net.InetSocketAddress; - -import android.os.SystemClock; - -import android.os.Handler; - -import org.kivy.android.PythonActivity; - -public class WebViewLoader { - private static final String TAG = "WebViewLoader"; - - public static void testConnection() { - - while (true) { - if (WebViewLoader.pingHost("localhost", {{ args.port }}, 100)) { - Log.v(TAG, "Successfully pinged localhost:{{ args.port }}"); - Handler mainHandler = new Handler(PythonActivity.mActivity.getMainLooper()); - - Runnable myRunnable = new Runnable() { - @Override - public void run() { - PythonActivity.mActivity.mWebView.loadUrl("http://127.0.0.1:{{ args.port }}/"); - Log.v(TAG, "Loaded webserver in webview"); - } - }; - mainHandler.post(myRunnable); - break; - - } else { - Log.v(TAG, "Could not ping localhost:{{ args.port }}"); - try { - Thread.sleep(100); - } catch(InterruptedException e) { - Log.v(TAG, "InterruptedException occurred when sleeping"); - } - } - } - } - - public static boolean pingHost(String host, int port, int timeout) { - Socket socket = new Socket(); - try { - socket.connect(new InetSocketAddress(host, port), timeout); - socket.close(); - return true; - } catch (IOException e) { - try {socket.close();} catch (IOException f) {return false;} - return false; // Either timeout or unreachable or failed DNS lookup. - } - } -} - - diff --git a/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml deleted file mode 100644 index 9ab301ad94..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/templates/build.tmpl.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml deleted file mode 100644 index 8b2f60c7e1..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/templates/custom_rules.tmpl.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - {% for dir, includes in args.extra_source_dirs %} - - {% endfor %} - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png b/pythonforandroid/bootstraps/service_only/build/templates/kivy-icon.png deleted file mode 100644 index 59a00ba6fff07cec43a4100fdf22f3f679df2349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16525 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 L>T(q_W}*KFob!<> diff --git a/pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg b/pythonforandroid/bootstraps/service_only/build/templates/kivy-presplash.jpg deleted file mode 100644 index 161ebc09284183771507c3eeb338cd5a7fefcb9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18251 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 - - {{ args.name }} - 0.1 - diff --git a/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml deleted file mode 100644 index 9564aae306..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/templates/test/build.tmpl.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl b/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl deleted file mode 100644 index 9564aae306..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/templates/test/build.xml.tmpl +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html b/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html deleted file mode 100644 index fbbeda0617..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/webview_includes/_load.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - Python WebView loader - - - - - - -
-
Loading...
-
- - -
-
- - - - - - From bfc0bb3ae8becfd37b2bfeebb333b0e77e62f0fd Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Tue, 21 Jun 2016 22:31:55 +0200 Subject: [PATCH 12/15] refactor kivy android java classes --- .../src/org/kivy/android/AssetExtract.java | 186 ++++++++++++++++++ .../src/org/kivy/android/PythonService.java | 134 ++++--------- .../src/org/kivy/android/PythonUtil.java | 70 +++---- .../src/org/renpy/android/AssetExtract.java | 117 ----------- 4 files changed, 249 insertions(+), 258 deletions(-) create mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java delete mode 100644 pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java new file mode 100644 index 0000000000..40d501c7d0 --- /dev/null +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java @@ -0,0 +1,186 @@ +package org.kivy.android; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.util.Log; + +import org.kamranzafar.jtar.TarEntry; +import org.kamranzafar.jtar.TarInputStream; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; + +public class AssetExtract { + private static String TAG = AssetExtract.class.getSimpleName(); + + /** + * @param parent File or directory to delete recursively + */ + public static void recursiveDelete(File parent) { + if (parent.isDirectory()) { + for (File child : parent.listFiles()) { + recursiveDelete(child); + } + } + parent.delete(); + } + + public static void extractAsset(Context ctx, String assetName, File target) { + Log.v(TAG, "Extract asset " + assetName + " to " + target.getAbsolutePath()); + + // The version of data in memory and on disk. + String packaged_version; + String disk_version; + + try { + PackageManager manager = ctx.getPackageManager(); + PackageInfo info = manager.getPackageInfo(ctx.getPackageName(), 0); + packaged_version = info.versionName; + + Log.v(TAG, "Data version is " + packaged_version); + } catch (PackageManager.NameNotFoundException e) { + packaged_version = null; + } + // If no packaged data version, no unpacking is necessary. + if (packaged_version == null) { + Log.w(TAG, "Data version not found"); + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String disk_version_fn = filesDir + "/" + assetName + ".version"; + + try { + byte buf[] = new byte[64]; + FileInputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + disk_version = ""; + } + + if (packaged_version.equals(disk_version)) { + Log.v(TAG, "Disk data version equals packaged data version."); + return; + } + + recursiveDelete(target); + target.mkdirs(); + + if (!extractTar(ctx.getAssets(), assetName, target.getAbsolutePath())) { + Log.e(TAG, "Could not extract " + assetName + " data."); + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(disk_version_fn); + os.write(packaged_version.getBytes()); + os.close(); + } catch (Exception ex) { + Log.w(TAG, ex); + } + } + + public static boolean extractTar(AssetManager assets, String assetName, String target) { + byte buf[] = new byte[1024 * 1024]; + + InputStream assetStream = null; + TarInputStream tis = null; + + try { + assetStream = assets.open(assetName, AssetManager.ACCESS_STREAMING); + tis = new TarInputStream(new BufferedInputStream( + new GZIPInputStream(new BufferedInputStream(assetStream, + 8192)), 8192)); + } catch (IOException e) { + Log.e(TAG, "opening up extract tar", e); + return false; + } + + while (true) { + TarEntry entry = null; + + try { + entry = tis.getNextEntry(); + } catch (java.io.IOException e) { + Log.e(TAG, "extracting tar", e); + return false; + } + + if (entry == null) { + break; + } + + Log.v(TAG, "extracting " + entry.getName()); + + if (entry.isDirectory()) { + + try { + new File(target + "/" + entry.getName()).mkdirs(); + } catch (SecurityException e) { + Log.e(TAG, "extracting tar", e); + } + + continue; + } + + OutputStream out = null; + String path = target + "/" + entry.getName(); + + try { + out = new BufferedOutputStream(new FileOutputStream(path), 8192); + } catch (FileNotFoundException e) { + Log.e(TAG, "extracting tar", e); + } catch (SecurityException e) { + Log.e(TAG, "extracting tar", e); + } + + if (out == null) { + Log.e(TAG, "could not open " + path); + return false; + } + + try { + while (true) { + int len = tis.read(buf); + + if (len == -1) { + break; + } + + out.write(buf, 0, len); + } + + out.flush(); + out.close(); + } catch (java.io.IOException e) { + Log.e(TAG, "extracting zip", e); + return false; + } + } + + try { + tis.close(); + assetStream.close(); + } catch (IOException e) { + // pass + } + + return true; + } +} \ No newline at end of file diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java index 8660bffe86..13c85b268f 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java @@ -2,23 +2,20 @@ import android.app.Service; import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.util.Log; -import org.renpy.android.AssetExtract; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; - public class PythonService extends Service implements Runnable { - private static String TAG = "PythonService"; + private static String TAG = PythonService.class.getSimpleName(); + + public static PythonService mService = null; + /** + * Intent that started the service + */ + private Intent startIntent = null; - // Thread for Python code private Thread pythonThread = null; // Python environment variables @@ -28,11 +25,7 @@ public class PythonService extends Service implements Runnable { private String pythonHome; private String pythonPath; private String serviceEntrypoint; - - // Argument to pass to Python code, private String pythonServiceArgument; - public static PythonService mService = null; - private Intent startIntent = null; private boolean autoRestartService = false; @@ -40,35 +33,36 @@ public void setAutoRestartService(boolean restart) { autoRestartService = restart; } - public boolean canDisplayNotification() { - return true; - } - - public int startType() { - return START_NOT_STICKY; - } - + /** + * {@inheritDoc} + */ @Override - public IBinder onBind(Intent arg0) { + public IBinder onBind(Intent intent) { return null; } + /** + * {@inheritDoc} + */ @Override public void onCreate() { Log.v(TAG, "Device: " + android.os.Build.DEVICE); Log.v(TAG, "Model: " + android.os.Build.MODEL); - unpackData("private", getFilesDir()); + AssetExtract.extractAsset(getApplicationContext(), "private.mp3", getFilesDir()); super.onCreate(); } + /** + * {@inheritDoc} + */ @Override public int onStartCommand(Intent intent, int flags, int startId) { if (pythonThread != null) { Log.v(TAG, "Service exists, do not start again"); return START_NOT_STICKY; } - startIntent = intent; + Bundle extras = intent.getExtras(); androidPrivate = extras.getString("androidPrivate"); androidArgument = extras.getString("androidArgument"); @@ -78,6 +72,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { pythonPath = extras.getString("pythonPath"); pythonServiceArgument = extras.getString("pythonServiceArgument"); + Log.v(TAG, "Starting Python thread"); pythonThread = new Thread(this); pythonThread.start(); @@ -104,6 +99,9 @@ protected void doStartForeground(Bundle extras) { startForeground(1, notification); } + /** + * {@inheritDoc} + */ @Override public void onDestroy() { super.onDestroy(); @@ -115,6 +113,9 @@ public void onDestroy() { Process.killProcess(Process.myPid()); } + /** + * {@inheritDoc} + */ @Override public void run() { PythonUtil.loadLibraries(getFilesDir()); @@ -124,80 +125,15 @@ public void run() { stopSelf(); } - public void recursiveDelete(File f) { - if (f.isDirectory()) { - for (File r : f.listFiles()) { - recursiveDelete(r); - } - } - f.delete(); - } - - public void unpackData(final String resource, File target) { - - Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); - - // The version of data in memory and on disk. - String data_version = null; - String disk_version = null; - - try { - PackageManager manager = this.getPackageManager(); - PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0); - data_version = info.versionName; - - Log.v(TAG, "Data version is " + data_version); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Data version not found of " + resource + " data."); - } - - // If no version, no unpacking is necessary. - if (data_version == null) { - return; - } - - // Check the current disk version, if any. - String filesDir = target.getAbsolutePath(); - String disk_version_fn = filesDir + "/" + resource + ".version"; - - try { - byte buf[] = new byte[64]; - FileInputStream is = new FileInputStream(disk_version_fn); - int len = is.read(buf); - disk_version = new String(buf, 0, len); - is.close(); - } catch (Exception e) { - disk_version = ""; - } - - // If the disk data is out of date, extract it and write the version - // file. - if (!data_version.equals(disk_version)) { - Log.v(TAG, "Extracting " + resource + " assets."); - - recursiveDelete(target); - target.mkdirs(); - - AssetExtract ae = new AssetExtract(this); - if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { - Log.e(TAG, "Could not extract " + resource + " data."); - } - - try { - // Write .nomedia. - new File(target, ".nomedia").createNewFile(); - - // Write version file. - FileOutputStream os = new FileOutputStream(disk_version_fn); - os.write(data_version.getBytes()); - os.close(); - } catch (Exception e) { - Log.w("python", e); - } - } - } - - // Native part + /** + * @param androidPrivate Directory for private files + * @param androidArgument Android path + * @param serviceEntrypoint Python file to execute first + * @param pythonName Python name + * @param pythonHome Python home + * @param pythonPath Python path + * @param pythonServiceArgument Argument to pass to Python code + */ public static native void nativeStart(String androidPrivate, String androidArgument, String serviceEntrypoint, String pythonName, String pythonHome, String pythonPath, diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java index 75d06e6582..692ee13b2c 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java @@ -1,48 +1,34 @@ package org.kivy.android; -import java.io.File; - import android.util.Log; -public class PythonUtil { - - protected static String[] getLibraries() { - return new String[] { "python2.7", "python3.5m", "main" }; - } - - public static void loadLibraries(File filesDir) { - - String filesDirPath = filesDir.getAbsolutePath(); - boolean skippedPython = false; - - for (String lib : getLibraries()) { - try { - System.loadLibrary(lib); - } catch (UnsatisfiedLinkError e) { - if (lib.startsWith("python") && !skippedPython) { - skippedPython = true; - continue; - } - throw e; - } - } - - try { - System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so"); - System.load(filesDirPath - + "/lib/python2.7/lib-dynload/unicodedata.so"); - } catch (UnsatisfiedLinkError e) { - Log.v("PythonUtil", - "Failed to load _io.so or unicodedata.so...but that's okay."); - } - - try { - // System.loadLibrary("ctypes"); - System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so"); - } catch (UnsatisfiedLinkError e) { - Log.v("PythonUtil", "Unsatisfied linker when loading ctypes"); - } +import java.io.File; - Log.v("PythonUtil", "Loaded everything!"); - } +public class PythonUtil { + private static String TAG = PythonUtil.class.getSimpleName(); + + protected static String[] getLibraries() { + return new String[]{ + "python2.7", + "main", + "/lib/python2.7/lib-dynload/_io.so", + "/lib/python2.7/lib-dynload/unicodedata.so", + "/lib/python2.7/lib-dynload/_ctypes.so", + }; + } + + public static void loadLibraries(File filesDir) { + String filesDirPath = filesDir.getAbsolutePath(); + Log.v(TAG, "Loading libraries from " + filesDirPath); + + for (String lib : getLibraries()) { + if (lib.startsWith("/")) { + System.load(filesDirPath + lib); + } else { + System.loadLibrary(lib); + } + } + + Log.v(TAG, "Loaded everything!"); + } } diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java deleted file mode 100644 index 09e20de2b8..0000000000 --- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/AssetExtract.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.renpy.android; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.zip.GZIPInputStream; - -import org.kamranzafar.jtar.TarEntry; -import org.kamranzafar.jtar.TarInputStream; - -import android.content.Context; -import android.content.res.AssetManager; -import android.util.Log; - -public class AssetExtract { - - private AssetManager mAssetManager = null; - - public AssetExtract(Context ctx) { - mAssetManager = ctx.getAssets(); - } - - public boolean extractTar(String asset, String target) { - - byte buf[] = new byte[1024 * 1024]; - - InputStream assetStream = null; - TarInputStream tis = null; - - try { - assetStream = mAssetManager.open(asset, - AssetManager.ACCESS_STREAMING); - tis = new TarInputStream(new BufferedInputStream( - new GZIPInputStream(new BufferedInputStream(assetStream, - 8192)), 8192)); - } catch (IOException e) { - Log.e("python", "opening up extract tar", e); - return false; - } - - while (true) { - TarEntry entry = null; - - try { - entry = tis.getNextEntry(); - } catch (java.io.IOException e) { - Log.e("python", "extracting tar", e); - return false; - } - - if (entry == null) { - break; - } - - Log.v("python", "extracting " + entry.getName()); - - if (entry.isDirectory()) { - - try { - new File(target + "/" + entry.getName()).mkdirs(); - } catch (SecurityException e) { - Log.e("python", "extracting tar", e); - } - - continue; - } - - OutputStream out = null; - String path = target + "/" + entry.getName(); - - try { - out = new BufferedOutputStream(new FileOutputStream(path), 8192); - } catch (FileNotFoundException e) { - Log.e("python", "extracting tar", e); - } catch (SecurityException e) { - Log.e("python", "extracting tar", e); - } - - if (out == null) { - Log.e("python", "could not open " + path); - return false; - } - - try { - while (true) { - int len = tis.read(buf); - - if (len == -1) { - break; - } - - out.write(buf, 0, len); - } - - out.flush(); - out.close(); - } catch (java.io.IOException e) { - Log.e("python", "extracting zip", e); - return false; - } - } - - try { - tis.close(); - assetStream.close(); - } catch (IOException e) { - // pass - } - - return true; - } -} From 6b30ca35b79e6fb7c0109738c733ebcba7141df9 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Tue, 21 Jun 2016 23:06:03 +0200 Subject: [PATCH 13/15] bugfix foreground service --- .../src/org/kivy/android/PythonService.java | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java index 13c85b268f..a9bc8c9a90 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java +++ b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java @@ -1,10 +1,14 @@ package org.kivy.android; +import android.app.Activity; +import android.app.PendingIntent; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.Process; +import android.support.v4.app.NotificationCompat; import android.util.Log; public class PythonService extends Service implements Runnable { @@ -33,6 +37,14 @@ public void setAutoRestartService(boolean restart) { autoRestartService = restart; } + public boolean canDisplayNotification() { + return true; + } + + public int startType() { + return START_NOT_STICKY; + } + /** * {@inheritDoc} */ @@ -76,30 +88,35 @@ public int onStartCommand(Intent intent, int flags, int startId) { pythonThread = new Thread(this); pythonThread.start(); - if (canDisplayNotification()) { - doStartForeground(extras); - } + if (canDisplayNotification()) { + doStartForeground(extras); + } return startType(); } - protected void doStartForeground(Bundle extras) { - String serviceTitle = extras.getString("serviceTitle"); - String serviceDescription = extras.getString("serviceDescription"); - - Context context = getApplicationContext(); - Notification notification = new Notification( - context.getApplicationInfo().icon, serviceTitle, - System.currentTimeMillis()); - Intent contextIntent = new Intent(context, PythonActivity.class); - PendingIntent pIntent = PendingIntent.getActivity(context, 0, - contextIntent, PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(context, serviceTitle, - serviceDescription, pIntent); - startForeground(1, notification); - } - - /** + protected void doStartForeground(Bundle extras) { + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + Context context = getApplicationContext(); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(this) + .setSmallIcon(context.getApplicationInfo().icon) + .setContentTitle(serviceTitle) + .setContentText(serviceDescription); + + int NOTIFICATION_ID = 1; + + Intent targetIntent = new Intent(this, Activity.class); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT); + builder.setContentIntent(contentIntent); + + startForeground(NOTIFICATION_ID, builder.build()); + } + + /** * {@inheritDoc} */ @Override From c96547684e5b9a9f420a3138222e0de496b23362 Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Wed, 22 Jun 2016 00:38:32 +0200 Subject: [PATCH 14/15] bugfix genericndkbuild --- pythonforandroid/bootstraps/service_only/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py index 1bade96f13..4a00e78c73 100644 --- a/pythonforandroid/bootstraps/service_only/__init__.py +++ b/pythonforandroid/bootstraps/service_only/__init__.py @@ -7,7 +7,7 @@ class ServiceOnlyBootstrap(Bootstrap): name = 'service_only' - recipe_depends = [('python2', 'python3crystax')] + recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')] def run_distribute(self): info_main('# Creating Android project from build and {} bootstrap'.format( From 842aedbbe03c756ca37a4ac845c9dcc6f864fc6a Mon Sep 17 00:00:00 2001 From: Paul Brussee Date: Wed, 22 Jun 2016 01:40:40 +0200 Subject: [PATCH 15/15] refactor python service template --- .../build/templates/Service.tmpl.java | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java index bf87996212..3523ecc114 100644 --- a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java @@ -2,15 +2,14 @@ import android.content.Intent; import android.content.Context; -import android.app.Notification; -import android.app.PendingIntent; -import android.os.Bundle; import org.kivy.android.PythonService; -import org.kivy.android.PythonActivity; public class Service{{ name|capitalize }} extends PythonService { {% if sticky %} + /** + * {@inheritDoc} + */ @Override public int startType() { return START_STICKY; @@ -18,39 +17,33 @@ public int startType() { {% endif %} {% if not foreground %} + /** + * {@inheritDoc} + */ @Override public boolean canDisplayNotification() { return false; } {% endif %} - @Override - protected void doStartForeground(Bundle extras) { - Context context = getApplicationContext(); - Notification notification = new Notification(context.getApplicationInfo().icon, - "{{ args.name }}", System.currentTimeMillis()); - Intent contextIntent = new Intent(context, PythonActivity.class); - PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); - startForeground({{ service_id }}, notification); - } - - static public void start(Context ctx, String pythonServiceArgument) { + public static void start(Context ctx, String pythonServiceArgument) { + String argument = ctx.getFilesDir().getAbsolutePath(); Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); - String argument = ctx.getFilesDir().getAbsolutePath(); intent.putExtra("androidPrivate", argument); intent.putExtra("androidArgument", argument); intent.putExtra("serviceEntrypoint", "{{ entrypoint }}"); + intent.putExtra("serviceTitle", "{{ name|capitalize }}"); + intent.putExtra("serviceDescription", ""); intent.putExtra("pythonName", "{{ name }}"); intent.putExtra("pythonHome", argument); intent.putExtra("pythonPath", argument + ":" + argument + "/lib"); intent.putExtra("pythonServiceArgument", pythonServiceArgument); ctx.startService(intent); } - - static public void stop(Context ctx) { + + public static void stop(Context ctx) { Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class); ctx.stopService(intent); } + }