Build process ported from Ant to Gradle
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
102
p4a/README.rst
Normal file
|
@ -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 <https://python-for-android.readthedocs.org/en/latest/quickstart/>`__
|
||||
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 <https://python-for-android.readthedocs.io/en/latest/quickstart/#usage>`__.
|
||||
|
||||
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 <http://kivy.org/docs/contribute.html>`__ 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.
|
|
@ -95,7 +95,7 @@ options (this list may not be exhaustive):
|
|||
``android:screenOrientation`` in the `Android documentation
|
||||
<https://developer.android.com/guide/topics/manifest/activity-element.html>`__.
|
||||
- ``--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.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 <https://developer.android.com/sdk/index.html#Other>`_
|
||||
- `Android SDK <https://developer.android.com/studio/index.html>`_
|
||||
- `Android NDK <https://developer.android.com/ndk/downloads/index.html>`_
|
||||
|
||||
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 <http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86.bin>`_
|
||||
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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``.
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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()
|
||||
|
|
14
p4a/pythonforandroid/bootstraps/lbry/build/.gitignore
vendored
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
||||
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)
|
||||
|
||||
# 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(
|
||||
'build.tmpl.xml',
|
||||
'build.xml',
|
||||
args=args,
|
||||
versioned_name=versioned_name)
|
||||
|
||||
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()
|
||||
|
|
BIN
p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -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
|
164
p4a/pythonforandroid/bootstraps/lbry/build/gradlew
vendored
Executable file
|
@ -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 "$@"
|
90
p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat
vendored
Normal file
|
@ -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
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
|
||||
<color name="red">#FF0000</color>
|
||||
<color name="green">#00C000</color>
|
||||
</resources>
|
|
@ -1,10 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">LbryControl</string>
|
||||
<string name="app_name">SDL App</string>
|
||||
<string name="private_version">0.1</string>
|
||||
|
||||
<string name="running">Running</string>
|
||||
<string name="service_status">Service Status</string>
|
||||
<string name="stopped">Stopped</string>
|
||||
<string name="start">START</string>
|
||||
<string name="stop">STOP</string>
|
||||
</resources>
|
||||
|
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64sp"
|
||||
android:layout_height="64sp"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="2sp"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/title"
|
||||
android:textSize="18sp"
|
||||
android:textColor="#fff"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:id="@+id/author"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World, SDLActivity"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:text="Please choose a project:"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4sp"
|
||||
/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/projectList"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
/>
|
||||
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyText"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4sp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
|
@ -19,7 +19,7 @@
|
|||
/>
|
||||
|
||||
<!-- Android 2.3.3 -->
|
||||
<uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ args.sdk_version }}" />
|
||||
<uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
|
@ -51,11 +51,6 @@
|
|||
|
||||
An example Java class can be found in README-android.txt
|
||||
-->
|
||||
<!--application android:label="@string/app_name"
|
||||
android:icon="@drawable/icon"
|
||||
android:allowBackup="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar{% if not args.window %}.Fullscreen{% endif %}"
|
||||
android:hardwareAccelerated="true"-->
|
||||
<application android:label="@string/app_name"
|
||||
android:icon="@drawable/icon"
|
||||
android:allowBackup="true"
|
||||
|
@ -66,11 +61,6 @@
|
|||
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
|
||||
<meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
|
||||
|
||||
<!--activity android:name="org.kivy.android.PythonActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
|
||||
android:screenOrientation="{{ args.orientation }}"
|
||||
-->
|
||||
<activity android:name="io.lbry.lbrynet.ServiceControlActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 %}
|
||||
}
|
|
@ -3,9 +3,9 @@
|
|||
<target name="-pre-build">
|
||||
<copy todir="tmp-src">
|
||||
{% if args.launcher %}
|
||||
<fileset dir="src" includes="**" />
|
||||
<fileset dir="src/main/java" includes="**" />
|
||||
{% else %}
|
||||
<fileset dir="src">
|
||||
<fileset dir="src/main/java">
|
||||
<exclude name="org/kivy/android/ProjectAdapter.java" />
|
||||
<exclude name="org/kivy/android/ProjectChooser.java" />
|
||||
</fileset>
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 11 KiB |
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">{{ args.name }}</string>
|
||||
<string name="private_version">0.1</string>
|
||||
<string name="private_version">{{ private_version }}</string>
|
||||
<string name="presplash_color">{{ args.presplash_color }}</string>
|
||||
<string name="urlScheme">{{ url_scheme }}</string>
|
||||
|
||||
|
|
|
@ -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/'))
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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()
|
||||
|
|
14
p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore
vendored
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
||||
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()
|
||||
|
|
BIN
p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -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
|
164
p4a/pythonforandroid/bootstraps/sdl2/build/gradlew
vendored
Executable file
|
@ -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 "$@"
|
90
p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat
vendored
Normal file
|
@ -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
|
|
@ -85,6 +85,11 @@ int main(int argc, char *argv[]) {
|
|||
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";
|
||||
setenv("PYTHON_NAME", "python", 1);
|
||||
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* File Types
|
||||
*
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
*
|
||||
* Ustar header
|
||||
*
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<br>
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -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] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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<String, Void, String> {
|
||||
@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<NewIntentListener> newIntentListeners = null;
|
||||
|
||||
public void registerNewIntentListener(NewIntentListener listener) {
|
||||
if ( this.newIntentListeners == null )
|
||||
this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
|
||||
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<NewIntentListener> 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<ActivityResultListener> activityResultListeners = null;
|
||||
|
||||
public void registerActivityResultListener(ActivityResultListener listener) {
|
||||
if ( this.activityResultListeners == null )
|
||||
this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
|
||||
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<ActivityResultListener> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<String> 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<String> getLibraries(File filesDir) {
|
||||
|
||||
String libsDirPath = filesDir.getParentFile().getParentFile().getAbsolutePath() + "/lib/";
|
||||
File libsDir = new File(libsDirPath);
|
||||
|
||||
ArrayList<String> libsList = new ArrayList<String>();
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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<Project> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// This string is autogenerated by ChangeAppSettings.sh, do not change
|
||||
// spaces amount
|
||||
package org.renpy.android;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import org.kamranzafar.jtar.*;
|
||||
|
||||
public class AssetExtract {
|
||||
|
||||
private AssetManager mAssetManager = null;
|
||||
private Activity mActivity = null;
|
||||
|
||||
public AssetExtract(Activity act) {
|
||||
mActivity = act;
|
||||
mAssetManager = act.getAssets();
|
||||
}
|
||||
|
||||
public boolean extractTar(String asset, String target) {
|
||||
|
||||
byte buf[] = new byte[1024 * 1024];
|
||||
|
||||
InputStream assetStream = null;
|
||||
TarInputStream tis = null;
|
||||
|
||||
try {
|
||||
assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
|
||||
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
|
||||
} catch (IOException e) {
|
||||
Log.e("python", "opening up extract tar", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
TarEntry entry = null;
|
||||
|
||||
try {
|
||||
entry = tis.getNextEntry();
|
||||
} catch ( java.io.IOException e ) {
|
||||
Log.e("python", "extracting tar", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( entry == null ) {
|
||||
break;
|
||||
}
|
||||
|
||||
Log.v("python", "extracting " + entry.getName());
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
|
||||
try {
|
||||
new File(target +"/" + entry.getName()).mkdirs();
|
||||
} catch ( SecurityException e ) { };
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
OutputStream out = null;
|
||||
String path = target + "/" + entry.getName();
|
||||
|
||||
try {
|
||||
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
|
||||
} catch ( FileNotFoundException e ) {
|
||||
} catch ( SecurityException e ) { };
|
||||
|
||||
if ( out == null ) {
|
||||
Log.e("python", "could not open " + path);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
int len = tis.read(buf);
|
||||
|
||||
if (len == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch ( java.io.IOException e ) {
|
||||
Log.e("python", "extracting zip", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
tis.close();
|
||||
assetStream.close();
|
||||
} catch (IOException e) {
|
||||
// pass
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
package org.renpy.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Vibrator;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import android.net.wifi.ScanResult;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import org.kivy.android.PythonActivity;
|
||||
|
||||
/**
|
||||
* Methods that are expected to be called via JNI, to access the
|
||||
* device's non-screen hardware. (For example, the vibration and
|
||||
* accelerometer.)
|
||||
*/
|
||||
public class Hardware {
|
||||
|
||||
// The context.
|
||||
static Context context;
|
||||
static View view;
|
||||
|
||||
/**
|
||||
* Vibrate for s seconds.
|
||||
*/
|
||||
public static void vibrate(double s) {
|
||||
Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (v != null) {
|
||||
v.vibrate((int) (1000 * s));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Overview of all Hardware Sensors of an Android Device
|
||||
*/
|
||||
public static String getHardwareSensors() {
|
||||
SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||
List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);
|
||||
|
||||
if (allSensors != null) {
|
||||
String resultString = "";
|
||||
for (Sensor s : allSensors) {
|
||||
resultString += String.format("Name=" + s.getName());
|
||||
resultString += String.format(",Vendor=" + s.getVendor());
|
||||
resultString += String.format(",Version=" + s.getVersion());
|
||||
resultString += String.format(",MaximumRange=" + s.getMaximumRange());
|
||||
// XXX MinDelay is not in the 2.2
|
||||
//resultString += String.format(",MinDelay=" + s.getMinDelay());
|
||||
resultString += String.format(",Power=" + s.getPower());
|
||||
resultString += String.format(",Type=" + s.getType() + "\n");
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors
|
||||
*/
|
||||
public static class generic3AxisSensor implements SensorEventListener {
|
||||
private final SensorManager sSensorManager;
|
||||
private final Sensor sSensor;
|
||||
private final int sSensorType;
|
||||
SensorEvent sSensorEvent;
|
||||
|
||||
public generic3AxisSensor(int sensorType) {
|
||||
sSensorType = sensorType;
|
||||
sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
sSensor = sSensorManager.getDefaultSensor(sSensorType);
|
||||
}
|
||||
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
}
|
||||
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
sSensorEvent = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the Sensor by registering/unregistering
|
||||
*/
|
||||
public void changeStatus(boolean enable) {
|
||||
if (enable) {
|
||||
sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
} else {
|
||||
sSensorManager.unregisterListener(this, sSensor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Sensor
|
||||
*/
|
||||
public float[] readSensor() {
|
||||
if (sSensorEvent != null) {
|
||||
return sSensorEvent.values;
|
||||
} else {
|
||||
float rv[] = { 0f, 0f, 0f };
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static generic3AxisSensor accelerometerSensor = null;
|
||||
public static generic3AxisSensor orientationSensor = null;
|
||||
public static generic3AxisSensor magneticFieldSensor = null;
|
||||
|
||||
/**
|
||||
* functions for backward compatibility reasons
|
||||
*/
|
||||
|
||||
public static void accelerometerEnable(boolean enable) {
|
||||
if ( accelerometerSensor == null )
|
||||
accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
accelerometerSensor.changeStatus(enable);
|
||||
}
|
||||
public static float[] accelerometerReading() {
|
||||
float rv[] = { 0f, 0f, 0f };
|
||||
if ( accelerometerSensor == null )
|
||||
return rv;
|
||||
return (float[]) accelerometerSensor.readSensor();
|
||||
}
|
||||
public static void orientationSensorEnable(boolean enable) {
|
||||
if ( orientationSensor == null )
|
||||
orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
|
||||
orientationSensor.changeStatus(enable);
|
||||
}
|
||||
public static float[] orientationSensorReading() {
|
||||
float rv[] = { 0f, 0f, 0f };
|
||||
if ( orientationSensor == null )
|
||||
return rv;
|
||||
return (float[]) orientationSensor.readSensor();
|
||||
}
|
||||
public static void magneticFieldSensorEnable(boolean enable) {
|
||||
if ( magneticFieldSensor == null )
|
||||
magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);
|
||||
magneticFieldSensor.changeStatus(enable);
|
||||
}
|
||||
public static float[] magneticFieldSensorReading() {
|
||||
float rv[] = { 0f, 0f, 0f };
|
||||
if ( magneticFieldSensor == null )
|
||||
return rv;
|
||||
return (float[]) magneticFieldSensor.readSensor();
|
||||
}
|
||||
|
||||
static public DisplayMetrics metrics = new DisplayMetrics();
|
||||
|
||||
/**
|
||||
* Get display DPI.
|
||||
*/
|
||||
public static int getDPI() {
|
||||
// AND: Shouldn't have to get the metrics like this every time...
|
||||
PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
return metrics.densityDpi;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Show the soft keyboard.
|
||||
// */
|
||||
// public static void showKeyboard(int input_type) {
|
||||
// //Log.i("python", "hardware.Java show_keyword " input_type);
|
||||
|
||||
// InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
// SDLSurfaceView vw = (SDLSurfaceView) view;
|
||||
|
||||
// int inputType = input_type;
|
||||
|
||||
// if (vw.inputType != inputType){
|
||||
// vw.inputType = inputType;
|
||||
// imm.restartInput(view);
|
||||
// }
|
||||
|
||||
// imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Hide the soft keyboard.
|
||||
*/
|
||||
public static void hideKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan WiFi networks
|
||||
*/
|
||||
static List<ScanResult> latestResult;
|
||||
|
||||
public static void enableWifiScanner()
|
||||
{
|
||||
IntentFilter i = new IntentFilter();
|
||||
i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
|
||||
|
||||
context.registerReceiver(new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context c, Intent i) {
|
||||
// Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs
|
||||
WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
|
||||
latestResult = w.getScanResults(); // Returns a <list> of scanResults
|
||||
}
|
||||
|
||||
}, i);
|
||||
|
||||
}
|
||||
|
||||
public static String scanWifi() {
|
||||
|
||||
// Now you can call this and it should execute the broadcastReceiver's
|
||||
// onReceive()
|
||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
boolean a = wm.startScan();
|
||||
|
||||
if (latestResult != null){
|
||||
|
||||
String latestResultString = "";
|
||||
for (ScanResult result : latestResult)
|
||||
{
|
||||
latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level);
|
||||
}
|
||||
|
||||
return latestResultString;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* network state
|
||||
*/
|
||||
|
||||
public static boolean network_state = false;
|
||||
|
||||
/**
|
||||
* Check network state directly
|
||||
*
|
||||
* (only one connection can be active at a given moment, detects all network type)
|
||||
*
|
||||
*/
|
||||
public static boolean checkNetwork()
|
||||
{
|
||||
boolean state = false;
|
||||
final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
|
||||
if (activeNetwork != null && activeNetwork.isConnected()) {
|
||||
state = true;
|
||||
} else {
|
||||
state = false;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* To recieve network state changes
|
||||
*/
|
||||
public static void registerNetworkCheck()
|
||||
{
|
||||
IntentFilter i = new IntentFilter();
|
||||
i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
context.registerReceiver(new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context c, Intent i) {
|
||||
network_state = checkNetwork();
|
||||
}
|
||||
|
||||
}, i);
|
||||
}
|
||||
|
||||
}
|