diff --git a/.travis.yml b/.travis.yml index 3e0a60e5..b1397eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,15 @@ install: - wget 'https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip' -P ~/.buildozer/android/platform/ - wget 'https://dl.google.com/android/android-sdk_r21-linux.tgz' -P ~/.buildozer/android/platform/ - wget 'https://dl.google.com/android/repository/android-21_r02.zip' -P ~/.buildozer/android/platform/ + - wget 'https://dl.google.com/android/repository/build-tools_r26.0.1-linux.zip' -P ~/.buildozer/android/platform/ - unzip -qq ~/.buildozer/android/platform/android-ndk-r13b-linux-x86_64.zip -d ~/.buildozer/android/platform/ - tar -xf ~/.buildozer/android/platform/android-sdk_r21-linux.tgz -C ~/.buildozer/android/platform/ - mv ~/.buildozer/android/platform/android-sdk-linux ~/.buildozer/android/platform/android-sdk-21 - unzip -qq ~/.buildozer/android/platform/android-21_r02.zip -d ~/.buildozer/android/platform/android-sdk-21/platforms - mv ~/.buildozer/android/platform/android-sdk-21/platforms/android-5.0.1 ~/.buildozer/android/platform/android-sdk-21/platforms/android-21 + - mkdir -p ~/.buildozer/android/platform/android-sdk-21/build-tools + - unzip -qq ~/.buildozer/android/platform/build-tools_r26.0.1-linux.zip -d ~/.buildozer/android/platform/android-sdk-21/build-tools + - mv ~/.buildozer/android/platform/android-sdk-21/build-tools/android-8.0.0 ~/.buildozer/android/platform/android-sdk-21/build-tools/26.0.1 script: - buildozer android debug | grep -v 'working:' --line-buffered - cp bin/*.apk /dev/null \ No newline at end of file diff --git a/buildozer.spec.sample b/buildozer.spec.sample index a0f7a48c..4075a59c 100644 --- a/buildozer.spec.sample +++ b/buildozer.spec.sample @@ -145,7 +145,7 @@ android.add_src = ./src/main/java # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) -#android.gradle_dependencies = +android.gradle_dependencies = com.android.support:appcompat-v7:21.0.3 # (str) python-for-android branch to use, defaults to master #p4a.branch = stable diff --git a/buildozer.spec.travis b/buildozer.spec.travis index a0f7a48c..4075a59c 100644 --- a/buildozer.spec.travis +++ b/buildozer.spec.travis @@ -145,7 +145,7 @@ android.add_src = ./src/main/java # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) -#android.gradle_dependencies = +android.gradle_dependencies = com.android.support:appcompat-v7:21.0.3 # (str) python-for-android branch to use, defaults to master #p4a.branch = stable diff --git a/p4a/LICENSE b/p4a/LICENSE index b336a3aa..d5d6b13c 100644 --- a/p4a/LICENSE +++ b/p4a/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2015 Kivy Team and other contributors +Copyright (c) 2010-2017 Kivy Team and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/p4a/README.rst b/p4a/README.rst new file mode 100644 index 00000000..63505a4c --- /dev/null +++ b/p4a/README.rst @@ -0,0 +1,102 @@ +python-for-android +================== + +python-for-android is a packager for Python apps on Android. You can +create your own Python distribution including the modules and +dependencies you want, and bundle it in an APK along with your own code. + +Features include: + +- Support for building with both Python 2 and Python 3. +- Different app backends including Kivy, PySDL2, and a WebView with + Python webserver. +- Automatic support for most pure Python modules, and built in support + for many others, including popular dependencies such as numpy and + sqlalchemy. +- Multiple architecture targets, for APKs optimised on any given + device. + +For documentation and support, see: + +- Website: http://python-for-android.readthedocs.io +- Mailing list: https://groups.google.com/forum/#!forum/kivy-users or + https://groups.google.com/forum/#!forum/python-android. + +In 2015 these tools were rewritten to provide a new, easier to use and +extend interface. If you are looking for the old toolchain with +distribute.sh and build.py, it is still available at +https://github.com/kivy/python-for-android/tree/old\_toolchain, and +issues and PRs relating to this branch are still accepted. However, the +new toolchain contains all the same functionality via the built in +pygame bootstrap. + +Documentation +============= + +Follow the `quickstart +instructions `__ +to install and begin creating APKs. + +Quick instructions to start would be:: + + pip install python-for-android + +or to test the master branch:: + + pip install git+https://github.com/kivy/python-for-android.git + +The executable is called ``python-for-android`` or ``p4a`` (both are +equivalent). To test that the installation worked, try:: + + python-for-android recipes + +This should return a list of recipes available to be built. + +To build any distributions, you need to set up the Android SDK and NDK +as described in the documentation linked above. + +If you did this, to build an APK with SDL2 you can try e.g.:: + + p4a apk --requirements=kivy --private /home/asandy/devel/planewave_frozen/ --package=net.inclem.planewavessdl2 --name="planewavessdl2" --version=0.5 --bootstrap=sdl2 + +For full instructions and parameter options, see `the +documentation `__. + +Support +======= + +If you need assistance, you can ask for help on our mailing list: + +- User Group: https://groups.google.com/group/kivy-users +- Email: kivy-users@googlegroups.com + +We also have an IRC channel: + +- Server: irc.freenode.net +- Port: 6667, 6697 (SSL only) +- Channel: #kivy + +Contributing +============ + +We love pull requests and discussing novel ideas. Check out our +`contribution guide `__ and feel +free to improve python-for-android. + +The following mailing list and IRC channel are used exclusively for +discussions about developing the Kivy framework and its sister projects: + +- Dev Group: https://groups.google.com/group/kivy-dev +- Email: kivy-dev@googlegroups.com + +IRC channel: + +- Server: irc.freenode.net +- Port: 6667, 6697 (SSL only) +- Channel: #kivy or #kivy-dev + +License +======= + +python-for-android is released under the terms of the MIT License. +Please refer to the LICENSE file. diff --git a/p4a/doc/source/buildoptions.rst b/p4a/doc/source/buildoptions.rst index a8ff5f56..bb95d396 100644 --- a/p4a/doc/source/buildoptions.rst +++ b/p4a/doc/source/buildoptions.rst @@ -95,7 +95,7 @@ options (this list may not be exhaustive): ``android:screenOrientation`` in the `Android documentation `__. - ``--icon``: A path to the png file to use as the application icon. -- ``-- permission``: A permission name for the app, +- ``--permission``: A permission name for the app, e.g. ``--permission VIBRATE``. For multiple permissions, add multiple ``--permission`` arguments. - ``--meta-data``: Custom key=value pairs to add in the application metadata. diff --git a/p4a/doc/source/distutils.rst b/p4a/doc/source/distutils.rst index 80926548..aaa15f76 100644 --- a/p4a/doc/source/distutils.rst +++ b/p4a/doc/source/distutils.rst @@ -15,7 +15,7 @@ example will include all .py and .png files in the ``testapp`` folder:: from distutils.core import setup - from setup + from setuptools import find_packages setup( name='testapp_setup', diff --git a/p4a/doc/source/old_toolchain/index.rst b/p4a/doc/source/old_toolchain/index.rst index cc7faa1a..1b873be6 100644 --- a/p4a/doc/source/old_toolchain/index.rst +++ b/p4a/doc/source/old_toolchain/index.rst @@ -7,6 +7,10 @@ using distribute.sh and build.py. This it entirely superseded by the new toolchain, you do not need to read it unless using this old method. +.. warning:: The old toolchain is deprecated and no longer + supported. You should instead use the :doc:`current version + <../quickstart>`. + Python for android is a project to create your own Python distribution including the modules you want, and create an apk including python, libs, and your application. diff --git a/p4a/doc/source/quickstart.rst b/p4a/doc/source/quickstart.rst index b67ae56c..129a1e14 100644 --- a/p4a/doc/source/quickstart.rst +++ b/p4a/doc/source/quickstart.rst @@ -84,15 +84,37 @@ Installing Android SDK You need to download and unpack the Android SDK and NDK to a directory (let's say $HOME/Documents/): -- `Android SDK `_ +- `Android SDK `_ - `Android NDK `_ +For the Android SDK, you can download 'just the command line +tools'. When you have extracted these you'll see only a directory +named ``tools``, and you will need to run extra commands to install +the SDK packages needed. + +For Android NDK, note that modern releases will only work on a 64-bit +operating system. If you are using a 32-bit distribution (or hardware), +the latest useable NDK version is r10e, which can be downloaded here: + +- `Legacy 32-bit Linux NDK r10e `_ + +First, install a platform to target (you can also replace ``19`` with +a different platform number, this will be used again later):: + + $SDK_DIR/tools/bin/sdkmanager "platforms;android-19" + +Second, install the build-tools. You can use +``$SDK_DIR/tools/bin/sdkmanager --list`` to see all the +possibilities, but 26.0.2 is the latest version at the time of writing:: + + $SDK_DIR/tools/bin/sdkmanager "build-tools;26.0.2" + Then, you can edit your ``~/.bashrc`` or other favorite shell to include new environment variables necessary for building on android:: # Adjust the paths! export ANDROIDSDK="$HOME/Documents/android-sdk-21" export ANDROIDNDK="$HOME/Documents/android-ndk-r10e" - export ANDROIDAPI="14" # Minimum API version your application require + export ANDROIDAPI="19" # Minimum API version your application require export ANDROIDNDKVER="r10e" # Version of the NDK you installed You have the possibility to configure on any command the PATH to the SDK, NDK and Android API using: @@ -100,7 +122,7 @@ You have the possibility to configure on any command the PATH to the SDK, NDK an - :code:`--sdk_dir PATH` as an equivalent of `$ANDROIDSDK` - :code:`--ndk_dir PATH` as an equivalent of `$ANDROIDNDK` - :code:`--android_api VERSION` as an equivalent of `$ANDROIDAPI` -- :code:`--ndk_ver PATH` as an equivalent of `$ANDROIDNDKVER` +- :code:`--ndk_version PATH` as an equivalent of `$ANDROIDNDKVER` Usage diff --git a/p4a/doc/source/services.rst b/p4a/doc/source/services.rst index d2ea1eed..a250c7bc 100644 --- a/p4a/doc/source/services.rst +++ b/p4a/doc/source/services.rst @@ -80,8 +80,10 @@ Services support a range of options and interactions not yet documented here but all accessible via calling other methods of the ``service`` reference. -.. note:: The app root directory for Python imports will be in the app -root folder even if the service file is in a subfolder. To import from -your service folder you must use e.g. ``import service.module`` -instead of ``import module``, if the service file is in the -``service/`` folder. +.. note:: + + The app root directory for Python imports will be in the app + root folder even if the service file is in a subfolder. To import from + your service folder you must use e.g. ``import service.module`` + instead of ``import module``, if the service file is in the + ``service/`` folder. diff --git a/p4a/doc/source/troubleshooting.rst b/p4a/doc/source/troubleshooting.rst index dcd4788f..975b29e3 100644 --- a/p4a/doc/source/troubleshooting.rst +++ b/p4a/doc/source/troubleshooting.rst @@ -160,3 +160,21 @@ This error appears in the logcat log if you try to access ``org.renpy.android.PythonActivity`` from within the new toolchain. To fix it, change your code to reference ``org.kivy.android.PythonActivity`` instead. + +websocket-client: if you see errors relating to 'SSL not available' +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ensure you have the package backports.ssl-match-hostname in the buildozer requirements, since Kivy targets python 2.7.x + +You may also need sslopt={"cert_reqs": ssl.CERT_NONE} as a parameter to ws.run_forever() if you get an error relating to host verification + +Requested API target 19 is not available, install it with the SDK android tool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This means that your SDK is missing the required platform tools. You +need to install the ``platforms;android-19`` package in your SDK, +using the ``android`` or ``sdkmanager`` tools (depending on SDK +version). + +If using buildozer this should be done automatically, but as a +workaround you can run these from +``~/.buildozer/android/platform/android-sdk-20/tools/android``. diff --git a/p4a/pythonforandroid/bootstrap.py b/p4a/pythonforandroid/bootstrap.py index c1b66333..f7aa6c9b 100644 --- a/p4a/pythonforandroid/bootstrap.py +++ b/p4a/pythonforandroid/bootstrap.py @@ -6,7 +6,7 @@ import json import importlib from pythonforandroid.logger import (warning, shprint, info, logger, - debug) + debug, error) from pythonforandroid.util import (current_directory, ensure_dir, temp_directory, which) from pythonforandroid.recipe import Recipe @@ -178,8 +178,6 @@ class Bootstrap(object): This is the only way you should access a bootstrap class, as it sets the bootstrap directory correctly. ''' - # AND: This method will need to check user dirs, and access - # bootstraps in a slightly different way if name is None: return None if not hasattr(cls, 'bootstraps'): @@ -195,20 +193,21 @@ class Bootstrap(object): bootstrap.ctx = ctx return bootstrap - def distribute_libs(self, arch, src_dirs, wildcard='*'): + def distribute_libs(self, arch, src_dirs, wildcard='*', dest_dir="libs"): '''Copy existing arch libs from build dirs to current dist dir.''' info('Copying libs') - tgt_dir = join('libs', arch.arch) + tgt_dir = join(dest_dir, arch.arch) ensure_dir(tgt_dir) for src_dir in src_dirs: for lib in glob.glob(join(src_dir, wildcard)): shprint(sh.cp, '-a', lib, tgt_dir) - def distribute_javaclasses(self, javaclass_dir): + def distribute_javaclasses(self, javaclass_dir, dest_dir="src"): '''Copy existing javaclasses from build dir to current dist dir.''' info('Copying java files') + ensure_dir(dest_dir) for filename in glob.glob(javaclass_dir): - shprint(sh.cp, '-a', filename, 'src') + shprint(sh.cp, '-a', filename, dest_dir) def distribute_aars(self, arch): '''Process existing .aar bundles and copy to current dist dir.''' diff --git a/p4a/pythonforandroid/bootstraps/lbry/__init__.py b/p4a/pythonforandroid/bootstraps/lbry/__init__.py index c16640fd..0cafc219 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/__init__.py +++ b/p4a/pythonforandroid/bootstraps/lbry/__init__.py @@ -1,67 +1,81 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir from os.path import join, exists, curdir, abspath from os import walk import glob import sh + +EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx") + + class LbryBootstrap(Bootstrap): name = 'lbry' recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')] def run_distribute(self): - info_main('# Creating Android project from build and {} bootstrap'.format( - self.name)) + info_main("# Creating Android project ({})".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) + arch = self.ctx.archs[0] + python_install_dir = self.ctx.get_python_install_dir() + from_crystax = self.ctx.python_recipe.from_crystax + crystax_python_dir = join("crystax_python", "crystax_python") + + if len(self.ctx.archs) > 1: + raise ValueError("LBRY/gradle support only one arch") + + info("Copying LBRY/gradle build for {}".format(arch)) + shprint(sh.rm, "-rf", self.dist_dir) + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environemnt variable (ANDROID_HOME) + # or the local.properties if exists 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') + 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') + if not exists("private") and not from_crystax: + ensure_dir("private") + if not exists("crystax_python") and from_crystax: + ensure_dir(crystax_python_dir) hostpython = sh.Command(self.ctx.hostpython) - if not self.ctx.python_recipe.from_crystax: + if not from_crystax: try: shprint(hostpython, '-OO', '-m', 'compileall', - self.ctx.get_python_install_dir(), + 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') + shprint( + sh.cp, '-a', 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) + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) - 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')) + if not 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/')) + libpymodules_fn = join("libs", arch.arch, "libpymodules.so") + if exists(libpymodules_fn): + shprint(sh.mv, libpymodules_fn, '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')) @@ -70,54 +84,53 @@ class LbryBootstrap(Bootstrap): 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'): + for dirname, root, filenames in walk("."): + for filename in filenames: + for suffix in EXCLUDE_EXTS: 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') + python_dir = join(ndk_dir, 'sources', 'python', + py_recipe.version, 'libs', arch.arch) + shprint(sh.cp, '-r', join(python_dir, + 'stdlib.zip'), crystax_python_dir) + shprint(sh.cp, '-r', join(python_dir, + 'modules'), crystax_python_dir) + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), + join(crystax_python_dir, '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('.') + site_packages_dir = join(crystax_python_dir, "site-packages") + find_ret = shprint( + sh.find, site_packages_dir, '-iname', '*.so') + filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1] + for filename in filenames: + parts = filename.split('.') if len(parts) <= 2: continue - shprint(sh.mv, filen, filen.split('.')[0] + '.so') + shprint(sh.mv, filename, filename.split('.')[0] + '.so') site_packages_dir = join(abspath(curdir), site_packages_dir) if 'sqlite3' not in self.ctx.recipe_build_order: with open('blacklist.txt', 'a') as fileh: fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - self.strip_libraries(arch) self.fry_eggs(site_packages_dir) super(LbryBootstrap, self).run_distribute() + bootstrap = LbryBootstrap() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore b/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore new file mode 100644 index 00000000..a1fc39c0 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore @@ -0,0 +1,14 @@ +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties b/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties index f74e644b..0dee5c8e 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties +++ b/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties @@ -16,3 +16,7 @@ # The password will be asked during the build when you use the 'release' target. source.absolute.dir = tmp-src + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py index b5dbbe28..f1e79c4b 100755 --- a/p4a/pythonforandroid/bootstraps/lbry/build/build.py +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py @@ -1,9 +1,10 @@ #!/usr/bin/env python2.7 +# coding: utf-8 from __future__ import print_function - -from os.path import dirname, join, isfile, realpath, relpath, split, exists -from os import makedirs, remove +from os.path import ( + dirname, join, isfile, realpath, relpath, split, exists, basename) +from os import makedirs, remove, listdir import os import tarfile import time @@ -11,19 +12,12 @@ import subprocess import shutil from zipfile import ZipFile import sys -import re +from distutils.version import LooseVersion from fnmatch import fnmatch import jinja2 -if os.name == 'nt': - ANDROID = 'android.bat' - ANT = 'ant.bat' -else: - ANDROID = 'android' - ANT = 'ant' - curdir = dirname(__file__) # Try to find a host version of Python that matches our ARM version. @@ -58,6 +52,17 @@ python_files = [] environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) + +def try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + def render(template, dest, **kwargs): '''Using jinja2, render `template` to the filename `dest`, supplying the @@ -109,6 +114,7 @@ def listfiles(d): for fn in listfiles(subdir): yield fn + def make_python_zip(): ''' Search for all the python related files, and construct the pythonXX.zip @@ -125,7 +131,6 @@ def make_python_zip(): global python_files d = realpath(join('private', 'lib', 'python2.7')) - def select(fn): if is_blacklist(fn): return False @@ -152,6 +157,7 @@ def make_python_zip(): zf.write(fn, afn) zf.close() + def make_tar(tfn, source_dirs, ignore_path=[]): ''' Make a zip file `fn` from the contents of source_dis. @@ -213,15 +219,6 @@ def compile_dir(dfn): def make_package(args): - # # Update the project to a recent version. - # try: - # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t', - # 'android-{}'.format(args.sdk_version)]) - # except (OSError, IOError): - # print('An error occured while calling', ANDROID, 'update') - # print('Your PATH must include android tools.') - # sys.exit(-1) - # Ignore warning if the launcher is in args if not args.launcher: if not (exists(join(realpath(args.private), 'main.py')) or @@ -233,18 +230,14 @@ main.py that loads it.''') exit(1) # Delete the old assets. - if exists('assets/public.mp3'): - os.unlink('assets/public.mp3') - - if exists('assets/private.mp3'): - os.unlink('assets/private.mp3') + try_unlink('src/main/assets/public.mp3') + try_unlink('src/main/assets/private.mp3') # In order to speedup import and initial depack, # construct a python27.zip make_python_zip() - # Package up the private and public data. - # AND: Just private for now + # Package up the private data (public not supported). tar_dirs = [args.private] if exists('private'): tar_dirs.append('private') @@ -252,42 +245,21 @@ main.py that loads it.''') tar_dirs.append('crystax_python') if args.private: - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) elif args.launcher: # clean 'None's as a result of main.py path absence tar_dirs = [tdir for tdir in tar_dirs if tdir] - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) - # else: - # make_tar('assets/private.mp3', ['private']) - - # if args.dir: - # make_tar('assets/public.mp3', [args.dir], args.ignore_path) - - - # # Build. - # try: - # for arg in args.command: - # subprocess.check_call([ANT, arg]) - # except (OSError, IOError): - # print 'An error occured while calling', ANT - # print 'Did you install ant on your system ?' - # sys.exit(-1) - + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) # folder name for launcher url_scheme = 'kivy' # Prepare some variables for templating process - if args.launcher: - default_icon = 'templates/launcher-icon.png' - default_presplash = 'templates/launcher-presplash.jpg' - else: - default_icon = 'templates/kivy-icon.png' - default_presplash = 'templates/kivy-presplash.jpg' - shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') - + default_icon = 'templates/kivy-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png') shutil.copy(args.presplash or default_presplash, - 'res/drawable/presplash.jpg') + 'src/main/res/drawable/presplash.jpg') # If extra Java jars were requested, copy them into the libs directory if args.add_jar: @@ -295,7 +267,18 @@ main.py that loads it.''') if not exists(jarname): print('Requested jar does not exist: {}'.format(jarname)) sys.exit(-1) - shutil.copy(jarname, 'libs') + shutil.copy(jarname, 'src/main/libs') + + # if extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) versioned_name = (args.name.replace(' ', '').replace('\'', '') + '-' + args.version) @@ -343,41 +326,57 @@ main.py that loads it.''') service_names.append(name) render( 'Service.tmpl.java', - 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), + 'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), name=name, entrypoint=entrypoint, args=args, foreground=foreground, sticky=sticky, - service_id=sid + 1, - ) + service_id=sid + 1) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + build_tools_versions = listdir(join(sdk_dir, 'build-tools')) + build_tools_versions = sorted(build_tools_versions, + key=LooseVersion) + build_tools_version = build_tools_versions[-1] + render( 'AndroidManifest.tmpl.xml', - 'AndroidManifest.xml', + 'src/main/AndroidManifest.xml', args=args, service=service, service_names=service_names, - url_scheme=url_scheme, - ) + android_api=android_api, + url_scheme=url_scheme) - render( - 'build.tmpl.xml', - 'build.xml', - args=args, - versioned_name=versioned_name) + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(join('src', 'main', 'AndroidManifest.xml'), + 'AndroidManifest.xml') + render( 'strings.tmpl.xml', - 'res/values/strings.xml', + 'src/main/res/values/strings.xml', args=args, url_scheme=url_scheme, - ) - + private_version=str(time.time())) + # add colors.xml render( 'colors.tmpl.xml', - 'res/values/colors.xml', + 'src/main/res/values/colors.xml', args=args, url_scheme=url_scheme, ) @@ -385,33 +384,39 @@ main.py that loads it.''') # add activity_service_control render( 'activity_service_control.xml', - 'res/layout/activity_service_control.xml', + 'src/main/res/layout/activity_service_control.xml', args=args, url_scheme=url_scheme, ) + ## gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + android_api=android_api, + build_tools_version=build_tools_version) + + ## ant build templates + render( + 'build.tmpl.xml', + 'build.xml', + args=args, + versioned_name=versioned_name) + render( 'custom_rules.tmpl.xml', 'custom_rules.xml', args=args) + if args.sign: render('build.properties', 'build.properties') else: if exists('build.properties'): os.remove('build.properties') - 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)) - - def parse_args(args=None): global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON default_android_api = 12 @@ -486,9 +491,15 @@ tools directory of the Android SDK. 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('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + ## The --sdk option has been removed, it is ignored in favour of + ## --android-api handled by toolchain.py ap.add_argument('--sdk', dest='sdk_version', default=-1, - type=int, help=('Android SDK version to use. Default to ' - 'the value of minsdk')) + type=int, help=('Deprecated argument, does nothing')) ap.add_argument('--minsdk', dest='min_sdk_version', default=default_android_api, type=int, help=('Minimum Android SDK version to use. Default to ' @@ -500,13 +511,14 @@ tools directory of the Android SDK. '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('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', help='Do not optimise .py files to .pyo.') ap.add_argument('--sign', action='store_true', @@ -521,12 +533,12 @@ tools directory of the Android SDK. if args.name and args.name[0] == '"' and args.name[-1] == '"': args.name = args.name[1:-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.sdk_version == -1: - args.sdk_version = args.min_sdk_version + if args.sdk_version != -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') if args.permissions is None: args.permissions = [] @@ -540,6 +552,18 @@ tools directory of the Android SDK. if args.services is None: args.services = [] + if args.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + if args.no_compile_pyo: PYTHON = None BLACKLIST_PATTERNS.remove('*.py') @@ -562,5 +586,4 @@ tools directory of the Android SDK. if __name__ == "__main__": - parse_args() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..3d0dee6e Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac1799fa --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradlew b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.xml deleted file mode 100644 index 6876df8f..00000000 --- a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - - #FF0000 - #00C000 - \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml index eed34f3d..daebceb9 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml @@ -1,10 +1,5 @@ + - LbryControl + SDL App 0.1 - - Running - Service Status - Stopped - START - STOP diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/Octal.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarConstants.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarEntry.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarHeader.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarInputStream.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarOutputStream.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarUtils.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiver.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonService.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonUtil.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonEvent.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonLock.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/Project.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectAdapter.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectChooser.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/libsdl/app/SDLActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/AssetExtract.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/AssetExtract.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/Hardware.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/Hardware.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonService.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/ResourceManager.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/ResourceManager.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/jniLibs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/libs/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/libs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..d50bdaae Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..0a299eb3 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..a336ad5c Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d423dac2 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml new file mode 100644 index 00000000..1823b132 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml new file mode 100644 index 00000000..123c4b6e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml new file mode 100644 index 00000000..23828e64 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml new file mode 100644 index 00000000..ee548142 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml index f5c35e8b..39125e25 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml @@ -19,7 +19,7 @@ /> - + @@ -51,12 +51,7 @@ An example Java class can be found in README-android.txt --> - - {% endfor %} - - {% endfor %} - diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java index 0b94ea72..3ed10c26 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java @@ -1,5 +1,8 @@ package {{ args.package }}; +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import android.content.Intent; import android.content.Context; import android.app.Notification; @@ -26,13 +29,31 @@ public class Service{{ name|capitalize }} extends PythonService { @Override protected void doStartForeground(Bundle extras) { + Notification notification; 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); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle("{{ args.name }}"); + builder.setContentText("{{ name| capitalize }}"); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } startForeground({{ service_id }}, notification); } diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle deleted file mode 100644 index 514ba0f8..00000000 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle +++ /dev/null @@ -1,59 +0,0 @@ -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 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle new file mode 100644 index 00000000..aaba011a --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle @@ -0,0 +1,70 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.1' + } +} + +allprojects { + repositories { + jcenter() + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion {{ android_api }} + buildToolsVersion '{{ build_tools_version }}' + defaultConfig { + minSdkVersion {{ args.min_sdk_version }} + targetSdkVersion {{ android_api }} + versionCode {{ args.numeric_version }} + versionName '{{ args.version }}' + } + + {% if args.sign -%} + signingConfigs { + release { + storeFile file(System.getenv("P4A_RELEASE_KEYSTORE")) + keyAlias System.getenv("P4A_RELEASE_KEYALIAS") + storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD") + keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD") + } + } + {%- endif %} + + buildTypes { + debug { + } + release { + {% if args.sign -%} + signingConfig signingConfigs.release + {%- endif %} + } + } + + sourceSets { + main { + jniLibs.srcDir 'libs' + } + } + +} + +dependencies { + {%- for aar in aars %} + compile(name: '{{ aar }}', ext: 'aar') + {%- endfor -%} + {%- if args.depends -%} + {%- for depend in args.depends %} + compile '{{ depend }}' + {%- endfor %} + {%- endif %} +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml index b2de7d6e..a6a7ebad 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml @@ -3,9 +3,9 @@ {% if args.launcher %} - + {% else %} - + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png index 59a00ba6..6ecb013b 100644 Binary files a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg index 161ebc09..c61efa27 100644 Binary files a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml index 04d36941..8e1abff8 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml @@ -1,10 +1,10 @@ {{ args.name }} - 0.1 + {{ private_version }} {{ args.presplash_color }} {{ url_scheme }} - + Running Service Status Stopped diff --git a/p4a/pythonforandroid/bootstraps/pygame/__init__.py b/p4a/pythonforandroid/bootstraps/pygame/__init__.py index cd340400..ec45482b 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/__init__.py +++ b/p4a/pythonforandroid/bootstraps/pygame/__init__.py @@ -46,7 +46,6 @@ class PygameBootstrap(Bootstrap): info('Copying python distribution') hostpython = sh.Command(self.ctx.hostpython) - # AND: This *doesn't* need to be in arm env? try: shprint(hostpython, '-OO', '-m', 'compileall', self.ctx.get_python_install_dir(), _tail=10, _filterout="^Listing") @@ -64,7 +63,6 @@ class PygameBootstrap(Bootstrap): shprint(sh.cp, '-a', join('python-install', 'lib'), 'private') shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7')) - # AND: Copylibs stuff should go here 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/')) diff --git a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java index 321ee609..07f9c886 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java +++ b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java @@ -1,5 +1,8 @@ package org.renpy.android; +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import android.app.Service; import android.os.IBinder; import android.os.Bundle; @@ -54,14 +57,31 @@ public class PythonService extends Service implements Runnable { pythonThread = new Thread(this); pythonThread.start(); + Notification notification; 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); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(serviceTitle); + builder.setContentText(serviceDescription); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } startForeground(1, notification); return START_NOT_STICKY; diff --git a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java index da7e87e6..d88783b4 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java +++ b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java @@ -706,6 +706,7 @@ public class SDLSurfaceView extends SurfaceView implements SurfaceHolder.Callbac nativeResize(mWidth, mHeight); nativeInitJavaCallbacks(); nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + nativeSetEnv("ANDROID_UNPACK", mFilesDirectory); nativeSetEnv("ANDROID_ARGUMENT", mArgument); nativeSetEnv("ANDROID_APP_PATH", mArgument); nativeSetEnv("PYTHONOPTIMIZE", "2"); diff --git a/p4a/pythonforandroid/bootstraps/sdl2/__init__.py b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py index 210bb989..95748a63 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py @@ -1,67 +1,81 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir from os.path import join, exists, curdir, abspath from os import walk import glob import sh -class SDL2Bootstrap(Bootstrap): - name = 'sdl2' + +EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx") + + +class SDL2GradleBootstrap(Bootstrap): + name = 'sdl2_gradle' recipe_depends = ['sdl2', ('python2', 'python3crystax')] def run_distribute(self): - info_main('# Creating Android project from build and {} bootstrap'.format( - self.name)) + info_main("# Creating Android project ({})".format(self.name)) - info('This currently just copies the SDL2 build stuff straight from the build dir.') - shprint(sh.rm, '-rf', self.dist_dir) - shprint(sh.cp, '-r', self.build_dir, self.dist_dir) + arch = self.ctx.archs[0] + python_install_dir = self.ctx.get_python_install_dir() + from_crystax = self.ctx.python_recipe.from_crystax + crystax_python_dir = join("crystax_python", "crystax_python") + + if len(self.ctx.archs) > 1: + raise ValueError("SDL2/gradle support only one arch") + + info("Copying SDL2/gradle build for {}".format(arch)) + shprint(sh.rm, "-rf", self.dist_dir) + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environemnt variable (ANDROID_HOME) + # or the local.properties if exists 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') + 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') + if not exists("private") and not from_crystax: + ensure_dir("private") + if not exists("crystax_python") and from_crystax: + ensure_dir(crystax_python_dir) hostpython = sh.Command(self.ctx.hostpython) - if not self.ctx.python_recipe.from_crystax: + if not from_crystax: try: shprint(hostpython, '-OO', '-m', 'compileall', - self.ctx.get_python_install_dir(), + 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') + shprint( + sh.cp, '-a', 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) + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) - 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')) + if not 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/')) + libpymodules_fn = join("libs", arch.arch, "libpymodules.so") + if exists(libpymodules_fn): + shprint(sh.mv, libpymodules_fn, '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')) @@ -70,54 +84,53 @@ class SDL2Bootstrap(Bootstrap): 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'): + for dirname, root, filenames in walk("."): + for filename in filenames: + for suffix in EXCLUDE_EXTS: 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') + python_dir = join(ndk_dir, 'sources', 'python', + py_recipe.version, 'libs', arch.arch) + shprint(sh.cp, '-r', join(python_dir, + 'stdlib.zip'), crystax_python_dir) + shprint(sh.cp, '-r', join(python_dir, + 'modules'), crystax_python_dir) + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), + join(crystax_python_dir, '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('.') + site_packages_dir = join(crystax_python_dir, "site-packages") + find_ret = shprint( + sh.find, site_packages_dir, '-iname', '*.so') + filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1] + for filename in filenames: + parts = filename.split('.') if len(parts) <= 2: continue - shprint(sh.mv, filen, filen.split('.')[0] + '.so') + shprint(sh.mv, filename, filename.split('.')[0] + '.so') site_packages_dir = join(abspath(curdir), site_packages_dir) if 'sqlite3' not in self.ctx.recipe_build_order: with open('blacklist.txt', 'a') as fileh: fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - self.strip_libraries(arch) self.fry_eggs(site_packages_dir) - super(SDL2Bootstrap, self).run_distribute() + super(SDL2GradleBootstrap, self).run_distribute() -bootstrap = SDL2Bootstrap() + +bootstrap = SDL2GradleBootstrap() diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore new file mode 100644 index 00000000..a1fc39c0 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore @@ -0,0 +1,14 @@ +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties b/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties index f74e644b..0dee5c8e 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties @@ -16,3 +16,7 @@ # The password will be asked during the build when you use the 'release' target. source.absolute.dir = tmp-src + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/build.py b/p4a/pythonforandroid/bootstraps/sdl2/build/build.py index 4a7f4668..12f7042d 100755 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/build.py +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/build.py @@ -1,9 +1,10 @@ #!/usr/bin/env python2.7 +# coding: utf-8 from __future__ import print_function - -from os.path import dirname, join, isfile, realpath, relpath, split, exists -from os import makedirs, remove +from os.path import ( + dirname, join, isfile, realpath, relpath, split, exists, basename) +from os import makedirs, remove, listdir import os import tarfile import time @@ -11,19 +12,12 @@ import subprocess import shutil from zipfile import ZipFile import sys -import re +from distutils.version import LooseVersion from fnmatch import fnmatch import jinja2 -if os.name == 'nt': - ANDROID = 'android.bat' - ANT = 'ant.bat' -else: - ANDROID = 'android' - ANT = 'ant' - curdir = dirname(__file__) # Try to find a host version of Python that matches our ARM version. @@ -58,6 +52,17 @@ python_files = [] environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) + +def try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + def render(template, dest, **kwargs): '''Using jinja2, render `template` to the filename `dest`, supplying the @@ -109,6 +114,7 @@ def listfiles(d): for fn in listfiles(subdir): yield fn + def make_python_zip(): ''' Search for all the python related files, and construct the pythonXX.zip @@ -125,7 +131,6 @@ def make_python_zip(): global python_files d = realpath(join('private', 'lib', 'python2.7')) - def select(fn): if is_blacklist(fn): return False @@ -152,6 +157,7 @@ def make_python_zip(): zf.write(fn, afn) zf.close() + def make_tar(tfn, source_dirs, ignore_path=[]): ''' Make a zip file `fn` from the contents of source_dis. @@ -213,15 +219,6 @@ def compile_dir(dfn): def make_package(args): - # # Update the project to a recent version. - # try: - # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t', - # 'android-{}'.format(args.sdk_version)]) - # except (OSError, IOError): - # print('An error occured while calling', ANDROID, 'update') - # print('Your PATH must include android tools.') - # sys.exit(-1) - # Ignore warning if the launcher is in args if not args.launcher: if not (exists(join(realpath(args.private), 'main.py')) or @@ -233,18 +230,14 @@ main.py that loads it.''') exit(1) # Delete the old assets. - if exists('assets/public.mp3'): - os.unlink('assets/public.mp3') - - if exists('assets/private.mp3'): - os.unlink('assets/private.mp3') + try_unlink('src/main/assets/public.mp3') + try_unlink('src/main/assets/private.mp3') # In order to speedup import and initial depack, # construct a python27.zip make_python_zip() - # Package up the private and public data. - # AND: Just private for now + # Package up the private data (public not supported). tar_dirs = [args.private] if exists('private'): tar_dirs.append('private') @@ -252,42 +245,21 @@ main.py that loads it.''') tar_dirs.append('crystax_python') if args.private: - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) elif args.launcher: # clean 'None's as a result of main.py path absence tar_dirs = [tdir for tdir in tar_dirs if tdir] - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) - # else: - # make_tar('assets/private.mp3', ['private']) - - # if args.dir: - # make_tar('assets/public.mp3', [args.dir], args.ignore_path) - - - # # Build. - # try: - # for arg in args.command: - # subprocess.check_call([ANT, arg]) - # except (OSError, IOError): - # print 'An error occured while calling', ANT - # print 'Did you install ant on your system ?' - # sys.exit(-1) - + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) # folder name for launcher url_scheme = 'kivy' # Prepare some variables for templating process - if args.launcher: - default_icon = 'templates/launcher-icon.png' - default_presplash = 'templates/launcher-presplash.jpg' - else: - default_icon = 'templates/kivy-icon.png' - default_presplash = 'templates/kivy-presplash.jpg' - shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') - + default_icon = 'templates/kivy-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png') shutil.copy(args.presplash or default_presplash, - 'res/drawable/presplash.jpg') + 'src/main/res/drawable/presplash.jpg') # If extra Java jars were requested, copy them into the libs directory if args.add_jar: @@ -295,7 +267,18 @@ main.py that loads it.''') if not exists(jarname): print('Requested jar does not exist: {}'.format(jarname)) sys.exit(-1) - shutil.copy(jarname, 'libs') + shutil.copy(jarname, 'src/main/libs') + + # if extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) versioned_name = (args.name.replace(' ', '').replace('\'', '') + '-' + args.version) @@ -343,59 +326,81 @@ main.py that loads it.''') service_names.append(name) render( 'Service.tmpl.java', - 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), + 'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), name=name, entrypoint=entrypoint, args=args, foreground=foreground, sticky=sticky, - service_id=sid + 1, - ) + service_id=sid + 1) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + build_tools_versions = listdir(join(sdk_dir, 'build-tools')) + build_tools_versions = sorted(build_tools_versions, + key=LooseVersion) + build_tools_version = build_tools_versions[-1] + render( 'AndroidManifest.tmpl.xml', - 'AndroidManifest.xml', + 'src/main/AndroidManifest.xml', args=args, service=service, service_names=service_names, - url_scheme=url_scheme, - ) + android_api=android_api, + url_scheme=url_scheme) + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(join('src', 'main', 'AndroidManifest.xml'), + 'AndroidManifest.xml') + + + render( + 'strings.tmpl.xml', + 'src/main/res/values/strings.xml', + args=args, + url_scheme=url_scheme, + private_version=str(time.time())) + + ## gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + android_api=android_api, + build_tools_version=build_tools_version) + + ## ant build templates render( 'build.tmpl.xml', 'build.xml', args=args, versioned_name=versioned_name) - render( - 'strings.tmpl.xml', - 'res/values/strings.xml', - args=args, - url_scheme=url_scheme, - ) - render( 'custom_rules.tmpl.xml', 'custom_rules.xml', args=args) + if args.sign: render('build.properties', 'build.properties') else: if exists('build.properties'): os.remove('build.properties') - 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)) - - def parse_args(args=None): global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON default_android_api = 12 @@ -470,9 +475,15 @@ tools directory of the Android SDK. 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('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + ## The --sdk option has been removed, it is ignored in favour of + ## --android-api handled by toolchain.py ap.add_argument('--sdk', dest='sdk_version', default=-1, - type=int, help=('Android SDK version to use. Default to ' - 'the value of minsdk')) + type=int, help=('Deprecated argument, does nothing')) ap.add_argument('--minsdk', dest='min_sdk_version', default=default_android_api, type=int, help=('Minimum Android SDK version to use. Default to ' @@ -484,13 +495,14 @@ tools directory of the Android SDK. '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('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', help='Do not optimise .py files to .pyo.') ap.add_argument('--sign', action='store_true', @@ -505,12 +517,12 @@ tools directory of the Android SDK. if args.name and args.name[0] == '"' and args.name[-1] == '"': args.name = args.name[1:-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.sdk_version == -1: - args.sdk_version = args.min_sdk_version + if args.sdk_version != -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') if args.permissions is None: args.permissions = [] @@ -524,6 +536,18 @@ tools directory of the Android SDK. if args.services is None: args.services = [] + if args.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + if args.no_compile_pyo: PYTHON = None BLACKLIST_PATTERNS.remove('*.py') @@ -546,5 +570,4 @@ tools directory of the Android SDK. if __name__ == "__main__": - parse_args() diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..3d0dee6e Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar differ diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac1799fa --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c index 7582348c..121d925d 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c @@ -84,6 +84,11 @@ int main(int argc, char *argv[]) { setenv("ANDROID_APP_PATH", env_argument, 1); env_entrypoint = getenv("ANDROID_ENTRYPOINT"); env_logname = getenv("PYTHON_NAME"); + + if (!getenv("ANDROID_UNPACK")) { + /* ANDROID_UNPACK currently isn't set in services */ + setenv("ANDROID_UNPACK", env_argument, 1); + } if (env_logname == NULL) { env_logname = "python"; @@ -104,12 +109,15 @@ int main(int argc, char *argv[]) { LOGP("Preparing to initialize python"); - if (dir_exists("crystax_python/")) { + char crystax_python_dir[256]; + snprintf(crystax_python_dir, 256, + "%s/crystax_python", getenv("ANDROID_UNPACK")); + if (dir_exists(crystax_python_dir)) { LOGP("crystax_python exists"); char paths[256]; snprintf(paths, 256, - "%s/crystax_python/stdlib.zip:%s/crystax_python/modules", - env_argument, env_argument); + "%s/stdlib.zip:%s/modules", + crystax_python_dir, crystax_python_dir); /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument, * env_argument); */ LOGP("calculated paths to be..."); @@ -166,11 +174,11 @@ int main(int argc, char *argv[]) { " argument ]\n"); } - if (dir_exists("crystax_python")) { + if (dir_exists(crystax_python_dir)) { char add_site_packages_dir[256]; snprintf(add_site_packages_dir, 256, - "sys.path.append('%s/crystax_python/site-packages')", - env_argument); + "sys.path.append('%s/site-packages')", + crystax_python_dir); PyRun_SimpleString("import sys\n" "sys.argv = ['notaninterpreterreally']\n" diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java new file mode 100755 index 00000000..dd10624e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java new file mode 100755 index 00000000..4611e20e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java new file mode 100755 index 00000000..fe01db46 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java new file mode 100755 index 00000000..b9d3a86b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java new file mode 100755 index 00000000..ec50a1b6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java new file mode 100755 index 00000000..ffdfe875 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java new file mode 100755 index 00000000..50165765 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java new file mode 100644 index 00000000..58a1c5ed --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java new file mode 100644 index 00000000..1a87c98b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java new file mode 100644 index 00000000..6a0c4d30 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java @@ -0,0 +1,480 @@ + +package org.kivy.android; + +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.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.AsyncTask; +import android.os.Bundle; +import android.os.PowerManager; +import android.graphics.PixelFormat; +import android.view.SurfaceHolder; +import android.content.Context; +import android.content.pm.ActivityInfo; +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.graphics.Color; + +import org.libsdl.app.SDLActivity; + +import org.kivy.android.PythonUtil; +import org.kivy.android.launcher.Project; + +import org.renpy.android.ResourceManager; +import org.renpy.android.AssetExtract; + + +public class PythonActivity extends SDLActivity { + private static final String TAG = "PythonActivity"; + + public static PythonActivity mActivity = null; + + private ResourceManager resourceManager = null; + private Bundle mMetaData = null; + private PowerManager.WakeLock mWakeLock = null; + + public String getAppRoot() { + String app_root = getFilesDir().getAbsolutePath() + "/app"; + return app_root; + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "My oncreate running"); + resourceManager = new ResourceManager(this); + + Log.v(TAG, "About to do super onCreate"); + super.onCreate(savedInstanceState); + Log.v(TAG, "Did super onCreate"); + + this.mActivity = this; + this.showLoadingScreen(); + + new UnpackFilesTask().execute(getAppRoot()); + } + + public void loadLibraries() { + String app_root = new String(getAppRoot()); + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file); + } + + 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) { + } + } + } + + private class UnpackFilesTask extends AsyncTask { + @Override + protected String doInBackground(String... params) { + File app_root_file = new File(params[0]); + Log.v(TAG, "Ready to unpack"); + unpackData("private", app_root_file); + return null; + } + + @Override + protected void onPostExecute(String result) { + // Figure out the directory where the game is. If the game was + // given to us via an intent, then we use the scheme-specific + // part of that intent to determine the file to launch. We + // also use the android.txt file to determine the orientation. + // + // Otherwise, we use the public data, if we have it, or the + // private data if we do not. + mActivity.finishLoad(); + + // finishLoad called setContentView with the SDL view, which + // removed the loading screen. However, we still need it to + // show until the app is ready to render, so pop it back up + // on top of the SDL view. + mActivity.showLoadingScreen(); + + String app_root_dir = getAppRoot(); + if (getIntent() != null && getIntent().getAction() != null && + getIntent().getAction().equals("org.kivy.LAUNCH")) { + File path = new File(getIntent().getData().getSchemeSpecificPart()); + + Project p = Project.scanDirectory(path); + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", p.dir + "/main.py"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", p.dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", p.dir); + + if (p != null) { + if (p.landscape) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + // Let old apps know they started. + try { + FileWriter f = new FileWriter(new File(path, ".launch")); + f.write("started"); + f.close(); + } catch (IOException e) { + // pass + } + } else { + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir); + } + + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); + Log.v(TAG, "Setting env vars for start.c and Python to use"); + SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + SDLActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir); + SDLActivity.nativeSetEnv("PYTHONHOME", app_root_dir); + SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib"); + SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2"); + + try { + Log.v(TAG, "Access to our meta-data..."); + mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo( + mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); + if ( mActivity.mMetaData.getInt("wakelock") == 1 ) { + mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + mActivity.mWakeLock.acquire(); + } + if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) { + Log.v(TAG, "Surface will be transparent."); + getSurface().setZOrderOnTop(true); + getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + Log.i(TAG, "Surface will NOT be transparent"); + } + } catch (PackageManager.NameNotFoundException e) { + } + } + + @Override + protected void onPreExecute() { + } + + @Override + protected void onProgressUpdate(Void... values) { + } + } + + 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; + } + + public static SurfaceView getSurface() { + return mSurface; + } + + //---------------------------------------------------------------------------- + // 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; + String app_root_dir = PythonActivity.mActivity.getAppRoot(); + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", app_root_dir); + serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo"); + serviceIntent.putExtra("pythonHome", app_root_dir); + serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/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); + } + + /** Loading screen implementation + * keepActive() is a method plugged in pollInputDevices in SDLActivity. + * Once it's called twice, the loading screen will be removed. + * The first call happen as soon as the window is created, but no image has been + * displayed first. My tests showed that we can wait one more. This might delay + * the real available of few hundred milliseconds. + * The real deal is to know if a rendering has already happen. The previous + * python-for-android and kivy was having something for that, but this new version + * is not compatible, and would require a new kivy version. + * In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called. + */ + public static ImageView mImageView = null; + int mLoadingCount = 2; + + @Override + public void keepActive() { + if (this.mLoadingCount > 0) { + this.mLoadingCount -= 1; + if (this.mLoadingCount == 0) { + this.removeLoadingScreen(); + } + } + } + + public void removeLoadingScreen() { + runOnUiThread(new Runnable() { + public void run() { + if (PythonActivity.mImageView != null && + PythonActivity.mImageView.getParent() != null) { + ((ViewGroup)PythonActivity.mImageView.getParent()).removeView( + PythonActivity.mImageView); + PythonActivity.mImageView = null; + } + } + }); + } + + + protected void showLoadingScreen() { + // load the bitmap + // 1. if the image is valid and we don't have layout yet, assign this bitmap + // as main view. + // 2. if we have a layout, just set it in the layout. + // 3. If we have an mImageView already, then do nothing because it will have + // already been made the content view or added to the layout. + + if (mImageView == null) { + int presplashId = this.resourceManager.getIdentifier("presplash", "drawable"); + InputStream is = this.getResources().openRawResource(presplashId); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(is); + } finally { + try { + is.close(); + } catch (IOException e) {}; + } + + mImageView = new ImageView(this); + mImageView.setImageBitmap(bitmap); + + /* + * Set the presplash loading screen background color + * https://developer.android.com/reference/android/graphics/Color.html + * Parse the color string, and return the corresponding color-int. + * If the string cannot be parsed, throws an IllegalArgumentException exception. + * Supported formats are: #RRGGBB #AARRGGBB or one of the following names: + * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', + * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia', + * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'. + */ + String backgroundColor = resourceManager.getString("presplash_color"); + if (backgroundColor != null) { + try { + mImageView.setBackgroundColor(Color.parseColor(backgroundColor)); + } catch (IllegalArgumentException e) {} + } + mImageView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + + } + + if (mLayout == null) { + setContentView(mImageView); + } else if (PythonActivity.mImageView.getParent() == null){ + mLayout.addView(mImageView); + } + + } + + @Override + protected void onPause() { + // fooabc + if ( this.mWakeLock != null && mWakeLock.isHeld()){ + this.mWakeLock.release(); + } + + Log.v(TAG, "onPause()"); + super.onPause(); + } + + @Override + protected void onResume() { + if ( this.mWakeLock != null){ + this.mWakeLock.acquire(); + } + Log.v(TAG, "onResume()"); + super.onResume(); + } + + + +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java new file mode 100644 index 00000000..046dc182 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java @@ -0,0 +1,153 @@ +package org.kivy.android; + +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +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 java.io.File; + +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"); + + Notification notification; + Context context = getApplicationContext(); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(serviceTitle); + builder.setContentText(serviceDescription); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } + 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(){ + String app_root = getFilesDir().getAbsolutePath() + "/app"; + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file); + 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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java new file mode 100644 index 00000000..6574dae7 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java @@ -0,0 +1,97 @@ +package org.kivy.android; + +import java.io.File; + +import android.util.Log; +import java.util.ArrayList; +import java.io.FilenameFilter; +import java.util.regex.Pattern; + +public class PythonUtil { + private static final String TAG = "pythonutil"; + + protected static void addLibraryIfExists(ArrayList libsList, String pattern, File libsDir) { + // pattern should be the name of the lib file, without the + // preceding "lib" or suffix ".so", for instance "ssl.*" will + // match files of the form "libssl.*.so". + File [] files = libsDir.listFiles(); + + pattern = "lib" + pattern + "\\.so"; + Pattern p = Pattern.compile(pattern); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + String name = file.getName(); + Log.v(TAG, "Checking pattern " + pattern + " against " + name); + if (p.matcher(name).matches()) { + Log.v(TAG, "Pattern " + pattern + " matched file " + name); + libsList.add(name.substring(3, name.length() - 3)); + } + } + } + + protected static ArrayList getLibraries(File filesDir) { + + String libsDirPath = filesDir.getParentFile().getParentFile().getAbsolutePath() + "/lib/"; + File libsDir = new File(libsDirPath); + + ArrayList libsList = new ArrayList(); + addLibraryIfExists(libsList, "crystax", libsDir); + addLibraryIfExists(libsList, "sqlite3", libsDir); + libsList.add("SDL2"); + libsList.add("SDL2_image"); + libsList.add("SDL2_mixer"); + libsList.add("SDL2_ttf"); + addLibraryIfExists(libsList, "ssl.*", libsDir); + addLibraryIfExists(libsList, "crypto.*", libsDir); + libsList.add("python2.7"); + libsList.add("python3.5m"); + libsList.add("main"); + return libsList; + } + + public static void loadLibraries(File filesDir) { + + String filesDirPath = filesDir.getAbsolutePath(); + boolean foundPython = false; + + for (String lib : getLibraries(filesDir)) { + Log.v(TAG, "Loading library: " + lib); + try { + System.loadLibrary(lib); + if (lib.startsWith("python")) { + foundPython = true; + } + } catch(UnsatisfiedLinkError e) { + // If this is the last possible libpython + // load, and it has failed, give a more + // general error + Log.v(TAG, "Library loading error: " + e.getMessage()); + if (lib.startsWith("python3.6") && !foundPython) { + throw new java.lang.RuntimeException("Could not load any libpythonXXX.so"); + } else if (lib.startsWith("python")) { + continue; + } else { + Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib); + 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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java new file mode 100644 index 00000000..9911356b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java new file mode 100644 index 00000000..22f9d903 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/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/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java new file mode 100644 index 00000000..9177b43b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java @@ -0,0 +1,99 @@ +package org.kivy.android.launcher; + +import java.io.UnsupportedEncodingException; +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +import android.util.Log; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + + +/** + * This represents a project we've scanned for. + */ +public class Project { + + public String dir = null; + String title = null; + String author = null; + Bitmap icon = null; + public boolean landscape = false; + + static String decode(String s) { + try { + return new String(s.getBytes("ISO-8859-1"), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return s; + } + } + + /** + * Scans directory for a android.txt file. If it finds one, + * and it looks valid enough, then it creates a new Project, + * and returns that. Otherwise, returns null. + */ + public static Project scanDirectory(File dir) { + + // We might have a link file. + if (dir.getAbsolutePath().endsWith(".link")) { + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + String directory = p.getProperty("directory", null); + + if (directory == null) { + return null; + } + + dir = new File(directory); + + } catch (Exception e) { + Log.i("Project", "Couldn't open link file " + dir, e); + } + } + + // Make sure we're dealing with a directory. + if (! dir.isDirectory()) { + return null; + } + + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + // Get the various properties. + String title = decode(p.getProperty("title", "Untitled")); + String author = decode(p.getProperty("author", "")); + boolean landscape = p.getProperty("orientation", "portrait").equals("landscape"); + + // Create the project object. + Project rv = new Project(); + rv.title = title; + rv.author = author; + rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath()); + rv.landscape = landscape; + rv.dir = dir.getAbsolutePath(); + + return rv; + + } catch (Exception e) { + Log.i("Project", "Couldn't open android.txt", e); + } + + return null; + + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java new file mode 100644 index 00000000..f66debfe --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java @@ -0,0 +1,44 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.view.Gravity; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.ImageView; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import org.renpy.android.ResourceManager; + +public class ProjectAdapter extends ArrayAdapter { + + private Activity mContext; + private ResourceManager resourceManager; + + public ProjectAdapter(Activity context) { + super(context, 0); + + mContext = context; + resourceManager = new ResourceManager(context); + } + + public View getView(int position, View convertView, ViewGroup parent) { + Project p = getItem(position); + + View v = resourceManager.inflateView("chooser_item"); + TextView title = (TextView) resourceManager.getViewById(v, "title"); + TextView author = (TextView) resourceManager.getViewById(v, "author"); + ImageView icon = (ImageView) resourceManager.getViewById(v, "icon"); + + title.setText(p.title); + author.setText(p.author); + icon.setImageBitmap(p.icon); + + return v; + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java new file mode 100644 index 00000000..17eec32f --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java @@ -0,0 +1,94 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.os.Bundle; + +import android.content.Intent; +import android.content.res.Resources; +import android.util.Log; +import android.view.View; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView; +import android.os.Environment; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import android.net.Uri; + +import org.renpy.android.ResourceManager; + +public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener { + + ResourceManager resourceManager; + + String urlScheme; + + @Override + public void onStart() + { + super.onStart(); + + resourceManager = new ResourceManager(this); + + urlScheme = resourceManager.getString("urlScheme"); + + // Set the window title. + setTitle(resourceManager.getString("appName")); + + // Scan the sdcard for files, and sort them. + File dir = new File(Environment.getExternalStorageDirectory(), urlScheme); + + File entries[] = dir.listFiles(); + + if (entries == null) { + entries = new File[0]; + } + + Arrays.sort(entries); + + // Create a ProjectAdapter and fill it with projects. + ProjectAdapter projectAdapter = new ProjectAdapter(this); + + // Populate it with the properties files. + for (File d : entries) { + Project p = Project.scanDirectory(d); + if (p != null) { + projectAdapter.add(p); + } + } + + if (projectAdapter.getCount() != 0) { + + View v = resourceManager.inflateView("project_chooser"); + ListView l = (ListView) resourceManager.getViewById(v, "projectList"); + + l.setAdapter(projectAdapter); + l.setOnItemClickListener(this); + + setContentView(v); + + } else { + + View v = resourceManager.inflateView("project_empty"); + TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText"); + + emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit."); + + setContentView(v); + } + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + Project p = (Project) parent.getItemAtPosition(position); + + Intent intent = new Intent( + "org.kivy.LAUNCH", + Uri.fromParts(urlScheme, p.dir, "")); + + intent.setClassName(getPackageName(), "org.kivy.android.PythonActivity"); + this.startActivity(intent); + this.finish(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java new file mode 100644 index 00000000..e1dc0846 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java @@ -0,0 +1,1579 @@ +package org.libsdl.app; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.lang.reflect.Method; + +import android.app.*; +import android.content.*; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.os.*; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.media.*; +import android.hardware.*; + +/** + SDL Activity +*/ +public class SDLActivity extends Activity { + private static final String TAG = "SDL"; + + // Keep track of the paused state + public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mExitCalledFromJava; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static View mTextEdit; + protected static ViewGroup mLayout; + protected static SDLJoystickHandler mJoystickHandler; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + // Audio + protected static AudioTrack mAudioTrack; + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } + } + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + + 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 + mSingleton = null; + mSurface = null; + mTextEdit = null; + mLayout = null; + mJoystickHandler = null; + mSDLThread = null; + mAudioTrack = null; + mExitCalledFromJava = false; + mBrokenLibraries = false; + mIsPaused = false; + mIsSurfaceReady = false; + mHasFocus = true; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v("SDL", "Device: " + android.os.Build.DEVICE); + Log.v("SDL", "Model: " + android.os.Build.MODEL); + Log.v("SDL", "onCreate():" + mSingleton); + super.onCreate(savedInstanceState); + + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + } + + // We don't do this in onCreate because we unpack and load the app data on a thread + // and we can't run setup tasks until that thread completes. + protected void finishLoad() { + // 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 start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up the surface + mSurface = new SDLSurface(getApplication()); + + if(Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } + else { + mJoystickHandler = new SDLJoystickHandler(); + } + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mSurface); + + setContentView(mLayout); + } + + // Events + @Override + protected void onPause() { + Log.v("SDL", "onPause()"); + super.onPause(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handlePause(); + } + + @Override + protected void onResume() { + Log.v("SDL", "onResume()"); + super.onResume(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleResume(); + } + + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.mHasFocus = hasFocus; + if (hasFocus) { + SDLActivity.handleResume(); + } + } + + @Override + public void onLowMemory() { + Log.v("SDL", "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v("SDL", "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + // Send a quit message to the application + SDLActivity.mExitCalledFromJava = true; + SDLActivity.nativeQuit(); + + // Now wait for the SDL thread to quit + if (SDLActivity.mSDLThread != null) { + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v("SDL", "Problem stopping thread: " + e); + } + SDLActivity.mSDLThread = null; + + //Log.v("SDL", "Finished waiting for SDL thread"); + } + + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + + // Completely closes application. + System.exit(0); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + + int keyCode = event.getKeyCode(); + // Ignore certain special keys so they're handled by Android + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_CAMERA || + keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ + keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed + * is the first to be called, mIsSurfaceReady should still be set + * to 'true' during the call to onPause (in a usual scenario). + */ + public static void handlePause() { + if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { + SDLActivity.mIsPaused = true; + SDLActivity.nativePause(); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + } + + /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. + * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume + * every time we get one of those events, only if it comes after surfaceDestroyed + */ + public static void handleResume() { + if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { + SDLActivity.mIsPaused = false; + SDLActivity.nativeResume(); + mSurface.handleResume(); + } + } + + /* The native thread has finished */ + public static void handleNativeExit() { + SDLActivity.mSDLThread = null; + mSingleton.finish(); + } + + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_UNUSED = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + protected boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + protected static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Context context = getContext(); + if (context == null) { + Log.e(TAG, "error handling message, getContext() returned null"); + return; + } + switch (msg.arg1) { + case COMMAND_CHANGE_TITLE: + if (context instanceof Activity) { + ((Activity) context).setTitle((String)msg.obj); + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + mTextEdit.setVisibility(View.GONE); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + break; + } + default: + if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { + Log.e(TAG, "error handling message, command is " + msg.arg1); + } + } + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + return commandHandler.sendMessage(msg); + } + + // C functions we call + public static native int nativeInit(Object arguments); + public static native void nativeLowMemory(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void onNativeResize(int x, int y, int format, float rate); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + public static native void nativeSetEnv(String j_name, String j_value); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native void nativeFlipBuffers(); + public static native int nativeAddJoystick(int device_id, String name, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); + + /** + * This method is called by SDL using JNI. + */ + public static void flipBuffers() { + SDLActivity.nativeFlipBuffers(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + // Called from SDLMain() thread and can't directly affect the view + return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return mSingleton; + } + + /** + * This method is called by SDL using JNI. + * @return result of getSystemService(name) but executed on UI thread. + */ + public Object getSystemServiceFromUiThread(final String name) { + final Object lock = new Object(); + final Object[] results = new Object[2]; // array for writable variables + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + results[0] = getSystemService(name); + results[1] = Boolean.TRUE; + lock.notify(); + } + } + }); + if (results[1] == null) { + try { + lock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + return results[0]; + } + + static class ShowTextInputTask implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputTask(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + @Override + public void run() { + AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( + w, h + HEIGHT_PADDING, x, y); + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + } + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return SDLActivity.mSurface.getNativeSurface(); + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e("SDL", "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioQuit() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack = null; + } + } + + // Input + + /** + * This method is called by SDL using JNI. + * @return an array which may be empty but is never null. + */ + public static int[] inputGetInputDeviceIds(int sources) { + int[] ids = InputDevice.getDeviceIds(); + int[] filtered = new int[ids.length]; + int used = 0; + for (int i = 0; i < ids.length; ++i) { + InputDevice device = InputDevice.getDevice(ids[i]); + if ((device != null) && ((device.getSources() & sources) != 0)) { + filtered[used++] = device.getId(); + } + } + return Arrays.copyOf(filtered, used); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + if (SDLActivity.mSDLThread != null) { + mJoystickHandler.pollInputDevices(); + SDLActivity.mSingleton.keepActive(); + } + } + + /** + * Trick needed for loading screen + */ + public void keepActive() { + } + + // APK extension files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + */ + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION")); + Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION")); + + try { + // To avoid direct dependency on Google APK extension library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, this, mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + ex.printStackTrace(); + fileStream = null; + } + + if (fileStream == null) { + throw new IOException(); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray