Switch build tool to Gradle #15

Merged
akinwale merged 2 commits from gradle-port into master 2017-12-21 08:24:32 +01:00
141 changed files with 6362 additions and 500 deletions

View file

@ -19,11 +19,17 @@ 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
- mkdir -p ~/.buildozer/android/platform/android-sdk-21/licenses
- echo $'\nd56f5187479451eabf01fb78af6dfcb131a6481e' > ~/.buildozer/android/platform/android-sdk-21/licenses/android-sdk-license
script:
- buildozer android debug | grep -v 'working:' --line-buffered
- cp bin/*.apk /dev/null

View file

@ -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

View file

@ -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

View file

@ -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
View 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.

View 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.

View file

@ -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',

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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``.

View file

@ -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.'''

View file

@ -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()

View 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

View file

@ -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

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python2.7
# coding: utf-8
from __future__ import print_function
from os.path import dirname, join, isfile, realpath, relpath, split, exists
from os import makedirs, remove
from os.path import (
dirname, join, isfile, realpath, relpath, split, exists, basename)
from os import makedirs, remove, listdir
import os
import tarfile
import time
@ -11,19 +12,12 @@ import subprocess
import shutil
from zipfile import ZipFile
import sys
import re
from distutils.version import LooseVersion
from fnmatch import fnmatch
import jinja2
if os.name == 'nt':
ANDROID = 'android.bat'
ANT = 'ant.bat'
else:
ANDROID = 'android'
ANT = 'ant'
curdir = dirname(__file__)
# Try to find a host version of Python that matches our ARM version.
@ -58,6 +52,17 @@ python_files = []
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
join(curdir, 'templates')))
def try_unlink(fn):
if exists(fn):
os.unlink(fn)
def ensure_dir(path):
if not exists(path):
makedirs(path)
def render(template, dest, **kwargs):
'''Using jinja2, render `template` to the filename `dest`, supplying the
@ -109,6 +114,7 @@ def listfiles(d):
for fn in listfiles(subdir):
yield fn
def make_python_zip():
'''
Search for all the python related files, and construct the pythonXX.zip
@ -125,7 +131,6 @@ def make_python_zip():
global python_files
d = realpath(join('private', 'lib', 'python2.7'))
def select(fn):
if is_blacklist(fn):
return False
@ -152,6 +157,7 @@ def make_python_zip():
zf.write(fn, afn)
zf.close()
def make_tar(tfn, source_dirs, ignore_path=[]):
'''
Make a zip file `fn` from the contents of source_dis.
@ -213,15 +219,6 @@ def compile_dir(dfn):
def make_package(args):
# # Update the project to a recent version.
# try:
# subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t',
# 'android-{}'.format(args.sdk_version)])
# except (OSError, IOError):
# print('An error occured while calling', ANDROID, 'update')
# print('Your PATH must include android tools.')
# sys.exit(-1)
# Ignore warning if the launcher is in args
if not args.launcher:
if not (exists(join(realpath(args.private), 'main.py')) or
@ -233,18 +230,14 @@ main.py that loads it.''')
exit(1)
# Delete the old assets.
if exists('assets/public.mp3'):
os.unlink('assets/public.mp3')
if exists('assets/private.mp3'):
os.unlink('assets/private.mp3')
try_unlink('src/main/assets/public.mp3')
try_unlink('src/main/assets/private.mp3')
# In order to speedup import and initial depack,
# construct a python27.zip
make_python_zip()
# Package up the private and public data.
# AND: Just private for now
# Package up the private data (public not supported).
tar_dirs = [args.private]
if exists('private'):
tar_dirs.append('private')
@ -252,42 +245,21 @@ main.py that loads it.''')
tar_dirs.append('crystax_python')
if args.private:
make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
elif args.launcher:
# clean 'None's as a result of main.py path absence
tar_dirs = [tdir for tdir in tar_dirs if tdir]
make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
# else:
# make_tar('assets/private.mp3', ['private'])
# if args.dir:
# make_tar('assets/public.mp3', [args.dir], args.ignore_path)
# # Build.
# try:
# for arg in args.command:
# subprocess.check_call([ANT, arg])
# except (OSError, IOError):
# print 'An error occured while calling', ANT
# print 'Did you install ant on your system ?'
# sys.exit(-1)
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
# folder name for launcher
url_scheme = 'kivy'
# Prepare some variables for templating process
if args.launcher:
default_icon = 'templates/launcher-icon.png'
default_presplash = 'templates/launcher-presplash.jpg'
else:
default_icon = 'templates/kivy-icon.png'
default_presplash = 'templates/kivy-presplash.jpg'
shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
default_icon = 'templates/kivy-icon.png'
default_presplash = 'templates/kivy-presplash.jpg'
shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png')
shutil.copy(args.presplash or default_presplash,
'res/drawable/presplash.jpg')
'src/main/res/drawable/presplash.jpg')
# If extra Java jars were requested, copy them into the libs directory
if args.add_jar:
@ -295,7 +267,18 @@ main.py that loads it.''')
if not exists(jarname):
print('Requested jar does not exist: {}'.format(jarname))
sys.exit(-1)
shutil.copy(jarname, 'libs')
shutil.copy(jarname, 'src/main/libs')
# if extra aar were requested, copy them into the libs directory
aars = []
if args.add_aar:
ensure_dir("libs")
for aarname in args.add_aar:
if not exists(aarname):
print('Requested aar does not exists: {}'.format(aarname))
sys.exit(-1)
shutil.copy(aarname, 'libs')
aars.append(basename(aarname).rsplit('.', 1)[0])
versioned_name = (args.name.replace(' ', '').replace('\'', '') +
'-' + args.version)
@ -343,41 +326,57 @@ main.py that loads it.''')
service_names.append(name)
render(
'Service.tmpl.java',
'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
name=name,
entrypoint=entrypoint,
args=args,
foreground=foreground,
sticky=sticky,
service_id=sid + 1,
)
service_id=sid + 1)
# Find the SDK directory and target API
with open('project.properties', 'r') as fileh:
target = fileh.read().strip()
android_api = target.split('-')[1]
with open('local.properties', 'r') as fileh:
sdk_dir = fileh.read().strip()
sdk_dir = sdk_dir[8:]
# Try to build with the newest available build tools
build_tools_versions = listdir(join(sdk_dir, 'build-tools'))
build_tools_versions = sorted(build_tools_versions,
key=LooseVersion)
build_tools_version = build_tools_versions[-1]
render(
'AndroidManifest.tmpl.xml',
'AndroidManifest.xml',
'src/main/AndroidManifest.xml',
args=args,
service=service,
service_names=service_names,
url_scheme=url_scheme,
)
android_api=android_api,
url_scheme=url_scheme)
render(
'build.tmpl.xml',
'build.xml',
args=args,
versioned_name=versioned_name)
# Copy the AndroidManifest.xml to the dist root dir so that ant
# can also use it
if exists('AndroidManifest.xml'):
remove('AndroidManifest.xml')
shutil.copy(join('src', 'main', 'AndroidManifest.xml'),
'AndroidManifest.xml')
render(
'strings.tmpl.xml',
'res/values/strings.xml',
'src/main/res/values/strings.xml',
args=args,
url_scheme=url_scheme,
)
private_version=str(time.time()))
# add colors.xml
render(
'colors.tmpl.xml',
'res/values/colors.xml',
'src/main/res/values/colors.xml',
args=args,
url_scheme=url_scheme,
)
@ -385,33 +384,39 @@ main.py that loads it.''')
# add activity_service_control
render(
'activity_service_control.xml',
'res/layout/activity_service_control.xml',
'src/main/res/layout/activity_service_control.xml',
args=args,
url_scheme=url_scheme,
)
## gradle build templates
render(
'build.tmpl.gradle',
'build.gradle',
args=args,
aars=aars,
android_api=android_api,
build_tools_version=build_tools_version)
## ant build templates
render(
'build.tmpl.xml',
'build.xml',
args=args,
versioned_name=versioned_name)
render(
'custom_rules.tmpl.xml',
'custom_rules.xml',
args=args)
if args.sign:
render('build.properties', 'build.properties')
else:
if exists('build.properties'):
os.remove('build.properties')
with open(join(dirname(__file__), 'res',
'values', 'strings.xml')) as fileh:
lines = fileh.read()
with open(join(dirname(__file__), 'res',
'values', 'strings.xml'), 'w') as fileh:
fileh.write(re.sub(r'"private_version">[0-9\.]*<',
'"private_version">{}<'.format(
str(time.time())), lines))
def parse_args(args=None):
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
default_android_api = 12
@ -486,9 +491,15 @@ tools directory of the Android SDK.
help=('Add a Java .jar to the libs, so you can access its '
'classes with pyjnius. You can specify this '
'argument more than once to include multiple jars'))
ap.add_argument('--add-aar', dest='add_aar', action='append',
help=('Add an aar dependency manually'))
ap.add_argument('--depend', dest='depends', action='append',
help=('Add a external dependency '
'(eg: com.android.support:appcompat-v7:19.0.1)'))
## The --sdk option has been removed, it is ignored in favour of
## --android-api handled by toolchain.py
ap.add_argument('--sdk', dest='sdk_version', default=-1,
type=int, help=('Android SDK version to use. Default to '
'the value of minsdk'))
type=int, help=('Deprecated argument, does nothing'))
ap.add_argument('--minsdk', dest='min_sdk_version',
default=default_android_api, type=int,
help=('Minimum Android SDK version to use. Default to '
@ -500,13 +511,14 @@ tools directory of the Android SDK.
'filename containing xml. The filename should be '
'located relative to the python-for-android '
'directory'))
ap.add_argument('--with-billing', dest='billing_pubkey',
help='If set, the billing service will be added (not implemented)')
ap.add_argument('--service', dest='services', action='append',
help='Declare a new service entrypoint: '
'NAME:PATH_TO_PY[:foreground]')
ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
help='Include additional source dirs in Java build')
ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',
action='store_true',
help='Use the system python during compileall if possible.')
ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true',
help='Do not optimise .py files to .pyo.')
ap.add_argument('--sign', action='store_true',
@ -521,12 +533,12 @@ tools directory of the Android SDK.
if args.name and args.name[0] == '"' and args.name[-1] == '"':
args.name = args.name[1:-1]
if args.billing_pubkey:
print('Billing not yet supported in sdl2 bootstrap!')
exit(1)
# if args.sdk_version == -1:
# args.sdk_version = args.min_sdk_version
if args.sdk_version == -1:
args.sdk_version = args.min_sdk_version
if args.sdk_version != -1:
print('WARNING: Received a --sdk argument, but this argument is '
'deprecated and does nothing.')
if args.permissions is None:
args.permissions = []
@ -540,6 +552,18 @@ tools directory of the Android SDK.
if args.services is None:
args.services = []
if args.try_system_python_compile:
# Hardcoding python2.7 is okay for now, as python3 skips the
# compilation anyway
if not exists('crystax_python'):
python_executable = 'python2.7'
try:
subprocess.call([python_executable, '--version'])
except (OSError, subprocess.CalledProcessError):
pass
else:
PYTHON = python_executable
if args.no_compile_pyo:
PYTHON = None
BLACKLIST_PATTERNS.remove('*.py')
@ -562,5 +586,4 @@ tools directory of the Android SDK.
if __name__ == "__main__":
parse_args()

View 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

View 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 "$@"

View 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

View file

@ -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>

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,12 +51,7 @@
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"
<application android:label="@string/app_name"
android:icon="@drawable/icon"
android:allowBackup="true"
android:theme="@android:style/Theme.Material.Light"
@ -66,12 +61,7 @@
<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"
<activity android:name="io.lbry.lbrynet.ServiceControlActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
android:screenOrientation="{{ args.orientation }}"
@ -116,7 +106,7 @@
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
android:process=":service_{{ name }}" />
{% endfor %}
<service android:name="{{ args.package }}.LbrynetService"
<service android:name="{{ args.package }}.LbrynetService"
android:process=":service_lbrynet" />
<service android:name="{{ args.package }}.LbrynetTestRunnerService"
android:process=":service_lbrynet_testrunner" />

View file

@ -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);
}

View file

@ -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

View file

@ -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 %}
}

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,10 +1,10 @@
<?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>
<string name="running">Running</string>
<string name="service_status">Service Status</string>
<string name="stopped">Stopped</string>

View file

@ -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/'))

View file

@ -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;

View file

@ -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");

View file

@ -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()

View 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

View file

@ -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

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python2.7
# coding: utf-8
from __future__ import print_function
from os.path import dirname, join, isfile, realpath, relpath, split, exists
from os import makedirs, remove
from os.path import (
dirname, join, isfile, realpath, relpath, split, exists, basename)
from os import makedirs, remove, listdir
import os
import tarfile
import time
@ -11,19 +12,12 @@ import subprocess
import shutil
from zipfile import ZipFile
import sys
import re
from distutils.version import LooseVersion
from fnmatch import fnmatch
import jinja2
if os.name == 'nt':
ANDROID = 'android.bat'
ANT = 'ant.bat'
else:
ANDROID = 'android'
ANT = 'ant'
curdir = dirname(__file__)
# Try to find a host version of Python that matches our ARM version.
@ -58,6 +52,17 @@ python_files = []
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
join(curdir, 'templates')))
def try_unlink(fn):
if exists(fn):
os.unlink(fn)
def ensure_dir(path):
if not exists(path):
makedirs(path)
def render(template, dest, **kwargs):
'''Using jinja2, render `template` to the filename `dest`, supplying the
@ -109,6 +114,7 @@ def listfiles(d):
for fn in listfiles(subdir):
yield fn
def make_python_zip():
'''
Search for all the python related files, and construct the pythonXX.zip
@ -125,7 +131,6 @@ def make_python_zip():
global python_files
d = realpath(join('private', 'lib', 'python2.7'))
def select(fn):
if is_blacklist(fn):
return False
@ -152,6 +157,7 @@ def make_python_zip():
zf.write(fn, afn)
zf.close()
def make_tar(tfn, source_dirs, ignore_path=[]):
'''
Make a zip file `fn` from the contents of source_dis.
@ -213,15 +219,6 @@ def compile_dir(dfn):
def make_package(args):
# # Update the project to a recent version.
# try:
# subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t',
# 'android-{}'.format(args.sdk_version)])
# except (OSError, IOError):
# print('An error occured while calling', ANDROID, 'update')
# print('Your PATH must include android tools.')
# sys.exit(-1)
# Ignore warning if the launcher is in args
if not args.launcher:
if not (exists(join(realpath(args.private), 'main.py')) or
@ -233,18 +230,14 @@ main.py that loads it.''')
exit(1)
# Delete the old assets.
if exists('assets/public.mp3'):
os.unlink('assets/public.mp3')
if exists('assets/private.mp3'):
os.unlink('assets/private.mp3')
try_unlink('src/main/assets/public.mp3')
try_unlink('src/main/assets/private.mp3')
# In order to speedup import and initial depack,
# construct a python27.zip
make_python_zip()
# Package up the private and public data.
# AND: Just private for now
# Package up the private data (public not supported).
tar_dirs = [args.private]
if exists('private'):
tar_dirs.append('private')
@ -252,42 +245,21 @@ main.py that loads it.''')
tar_dirs.append('crystax_python')
if args.private:
make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
elif args.launcher:
# clean 'None's as a result of main.py path absence
tar_dirs = [tdir for tdir in tar_dirs if tdir]
make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
# else:
# make_tar('assets/private.mp3', ['private'])
# if args.dir:
# make_tar('assets/public.mp3', [args.dir], args.ignore_path)
# # Build.
# try:
# for arg in args.command:
# subprocess.check_call([ANT, arg])
# except (OSError, IOError):
# print 'An error occured while calling', ANT
# print 'Did you install ant on your system ?'
# sys.exit(-1)
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
# folder name for launcher
url_scheme = 'kivy'
# Prepare some variables for templating process
if args.launcher:
default_icon = 'templates/launcher-icon.png'
default_presplash = 'templates/launcher-presplash.jpg'
else:
default_icon = 'templates/kivy-icon.png'
default_presplash = 'templates/kivy-presplash.jpg'
shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
default_icon = 'templates/kivy-icon.png'
default_presplash = 'templates/kivy-presplash.jpg'
shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png')
shutil.copy(args.presplash or default_presplash,
'res/drawable/presplash.jpg')
'src/main/res/drawable/presplash.jpg')
# If extra Java jars were requested, copy them into the libs directory
if args.add_jar:
@ -295,7 +267,18 @@ main.py that loads it.''')
if not exists(jarname):
print('Requested jar does not exist: {}'.format(jarname))
sys.exit(-1)
shutil.copy(jarname, 'libs')
shutil.copy(jarname, 'src/main/libs')
# if extra aar were requested, copy them into the libs directory
aars = []
if args.add_aar:
ensure_dir("libs")
for aarname in args.add_aar:
if not exists(aarname):
print('Requested aar does not exists: {}'.format(aarname))
sys.exit(-1)
shutil.copy(aarname, 'libs')
aars.append(basename(aarname).rsplit('.', 1)[0])
versioned_name = (args.name.replace(' ', '').replace('\'', '') +
'-' + args.version)
@ -343,59 +326,81 @@ main.py that loads it.''')
service_names.append(name)
render(
'Service.tmpl.java',
'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
name=name,
entrypoint=entrypoint,
args=args,
foreground=foreground,
sticky=sticky,
service_id=sid + 1,
)
service_id=sid + 1)
# Find the SDK directory and target API
with open('project.properties', 'r') as fileh:
target = fileh.read().strip()
android_api = target.split('-')[1]
with open('local.properties', 'r') as fileh:
sdk_dir = fileh.read().strip()
sdk_dir = sdk_dir[8:]
# Try to build with the newest available build tools
build_tools_versions = listdir(join(sdk_dir, 'build-tools'))
build_tools_versions = sorted(build_tools_versions,
key=LooseVersion)
build_tools_version = build_tools_versions[-1]
render(
'AndroidManifest.tmpl.xml',
'AndroidManifest.xml',
'src/main/AndroidManifest.xml',
args=args,
service=service,
service_names=service_names,
url_scheme=url_scheme,
)
android_api=android_api,
url_scheme=url_scheme)
# Copy the AndroidManifest.xml to the dist root dir so that ant
# can also use it
if exists('AndroidManifest.xml'):
remove('AndroidManifest.xml')
shutil.copy(join('src', 'main', 'AndroidManifest.xml'),
'AndroidManifest.xml')
render(
'strings.tmpl.xml',
'src/main/res/values/strings.xml',
args=args,
url_scheme=url_scheme,
private_version=str(time.time()))
## gradle build templates
render(
'build.tmpl.gradle',
'build.gradle',
args=args,
aars=aars,
android_api=android_api,
build_tools_version=build_tools_version)
## ant build templates
render(
'build.tmpl.xml',
'build.xml',
args=args,
versioned_name=versioned_name)
render(
'strings.tmpl.xml',
'res/values/strings.xml',
args=args,
url_scheme=url_scheme,
)
render(
'custom_rules.tmpl.xml',
'custom_rules.xml',
args=args)
if args.sign:
render('build.properties', 'build.properties')
else:
if exists('build.properties'):
os.remove('build.properties')
with open(join(dirname(__file__), 'res',
'values', 'strings.xml')) as fileh:
lines = fileh.read()
with open(join(dirname(__file__), 'res',
'values', 'strings.xml'), 'w') as fileh:
fileh.write(re.sub(r'"private_version">[0-9\.]*<',
'"private_version">{}<'.format(
str(time.time())), lines))
def parse_args(args=None):
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
default_android_api = 12
@ -470,9 +475,15 @@ tools directory of the Android SDK.
help=('Add a Java .jar to the libs, so you can access its '
'classes with pyjnius. You can specify this '
'argument more than once to include multiple jars'))
ap.add_argument('--add-aar', dest='add_aar', action='append',
help=('Add an aar dependency manually'))
ap.add_argument('--depend', dest='depends', action='append',
help=('Add a external dependency '
'(eg: com.android.support:appcompat-v7:19.0.1)'))
## The --sdk option has been removed, it is ignored in favour of
## --android-api handled by toolchain.py
ap.add_argument('--sdk', dest='sdk_version', default=-1,
type=int, help=('Android SDK version to use. Default to '
'the value of minsdk'))
type=int, help=('Deprecated argument, does nothing'))
ap.add_argument('--minsdk', dest='min_sdk_version',
default=default_android_api, type=int,
help=('Minimum Android SDK version to use. Default to '
@ -484,13 +495,14 @@ tools directory of the Android SDK.
'filename containing xml. The filename should be '
'located relative to the python-for-android '
'directory'))
ap.add_argument('--with-billing', dest='billing_pubkey',
help='If set, the billing service will be added (not implemented)')
ap.add_argument('--service', dest='services', action='append',
help='Declare a new service entrypoint: '
'NAME:PATH_TO_PY[:foreground]')
ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
help='Include additional source dirs in Java build')
ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',
action='store_true',
help='Use the system python during compileall if possible.')
ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true',
help='Do not optimise .py files to .pyo.')
ap.add_argument('--sign', action='store_true',
@ -505,12 +517,12 @@ tools directory of the Android SDK.
if args.name and args.name[0] == '"' and args.name[-1] == '"':
args.name = args.name[1:-1]
if args.billing_pubkey:
print('Billing not yet supported in sdl2 bootstrap!')
exit(1)
# if args.sdk_version == -1:
# args.sdk_version = args.min_sdk_version
if args.sdk_version == -1:
args.sdk_version = args.min_sdk_version
if args.sdk_version != -1:
print('WARNING: Received a --sdk argument, but this argument is '
'deprecated and does nothing.')
if args.permissions is None:
args.permissions = []
@ -524,6 +536,18 @@ tools directory of the Android SDK.
if args.services is None:
args.services = []
if args.try_system_python_compile:
# Hardcoding python2.7 is okay for now, as python3 skips the
# compilation anyway
if not exists('crystax_python'):
python_executable = 'python2.7'
try:
subprocess.call([python_executable, '--version'])
except (OSError, subprocess.CalledProcessError):
pass
else:
PYTHON = python_executable
if args.no_compile_pyo:
PYTHON = None
BLACKLIST_PATTERNS.remove('*.py')
@ -546,5 +570,4 @@ tools directory of the Android SDK.
if __name__ == "__main__":
parse_args()

View 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

View 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 "$@"

View 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

View file

@ -84,6 +84,11 @@ int main(int argc, char *argv[]) {
setenv("ANDROID_APP_PATH", env_argument, 1);
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
env_logname = getenv("PYTHON_NAME");
if (!getenv("ANDROID_UNPACK")) {
/* ANDROID_UNPACK currently isn't set in services */
setenv("ANDROID_UNPACK", env_argument, 1);
}
if (env_logname == NULL) {
env_logname = "python";
@ -104,12 +109,15 @@ int main(int argc, char *argv[]) {
LOGP("Preparing to initialize python");
if (dir_exists("crystax_python/")) {
char crystax_python_dir[256];
snprintf(crystax_python_dir, 256,
"%s/crystax_python", getenv("ANDROID_UNPACK"));
if (dir_exists(crystax_python_dir)) {
LOGP("crystax_python exists");
char paths[256];
snprintf(paths, 256,
"%s/crystax_python/stdlib.zip:%s/crystax_python/modules",
env_argument, env_argument);
"%s/stdlib.zip:%s/modules",
crystax_python_dir, crystax_python_dir);
/* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
* env_argument); */
LOGP("calculated paths to be...");
@ -166,11 +174,11 @@ int main(int argc, char *argv[]) {
" argument ]\n");
}
if (dir_exists("crystax_python")) {
if (dir_exists(crystax_python_dir)) {
char add_site_packages_dir[256];
snprintf(add_site_packages_dir, 256,
"sys.path.append('%s/crystax_python/site-packages')",
env_argument);
"sys.path.append('%s/site-packages')",
crystax_python_dir);
PyRun_SimpleString("import sys\n"
"sys.argv = ['notaninterpreterreally']\n"

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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] );
}
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
};

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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!");
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show more