Android service control activity implementation
|
@ -200,7 +200,7 @@ p4a.local_recipes = ~/Dev/Python/lbry-android/recipes
|
||||||
#p4a.hook =
|
#p4a.hook =
|
||||||
|
|
||||||
# (str) Bootstrap to use for android builds
|
# (str) Bootstrap to use for android builds
|
||||||
# p4a.bootstrap = sdl2
|
p4a.bootstrap = lbry
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -200,7 +200,7 @@ p4a.local_recipes = ./recipes
|
||||||
#p4a.hook =
|
#p4a.hook =
|
||||||
|
|
||||||
# (str) Bootstrap to use for android builds
|
# (str) Bootstrap to use for android builds
|
||||||
# p4a.bootstrap = sdl2
|
p4a.bootstrap = lbry
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
123
p4a/pythonforandroid/bootstraps/lbry/__init__.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main
|
||||||
|
from os.path import join, exists, curdir, abspath
|
||||||
|
from os import walk
|
||||||
|
import glob
|
||||||
|
import sh
|
||||||
|
|
||||||
|
class LbryBootstrap(Bootstrap):
|
||||||
|
name = 'lbry'
|
||||||
|
|
||||||
|
recipe_depends = ['sdl2', ('python2', 'python3crystax')]
|
||||||
|
|
||||||
|
def run_distribute(self):
|
||||||
|
info_main('# Creating Android project from build and {} bootstrap'.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)
|
||||||
|
with current_directory(self.dist_dir):
|
||||||
|
with open('local.properties', 'w') as fileh:
|
||||||
|
fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
|
||||||
|
|
||||||
|
arch = self.ctx.archs[0]
|
||||||
|
if len(self.ctx.archs) > 1:
|
||||||
|
raise ValueError('built for more than one arch, but bootstrap cannot handle that yet')
|
||||||
|
info('Bootstrap running with arch {}'.format(arch))
|
||||||
|
|
||||||
|
with current_directory(self.dist_dir):
|
||||||
|
info('Copying python distribution')
|
||||||
|
|
||||||
|
if not exists('private') and not self.ctx.python_recipe.from_crystax:
|
||||||
|
shprint(sh.mkdir, 'private')
|
||||||
|
if not exists('crystax_python') and self.ctx.python_recipe.from_crystax:
|
||||||
|
shprint(sh.mkdir, 'crystax_python')
|
||||||
|
shprint(sh.mkdir, 'crystax_python/crystax_python')
|
||||||
|
if not exists('assets'):
|
||||||
|
shprint(sh.mkdir, 'assets')
|
||||||
|
|
||||||
|
hostpython = sh.Command(self.ctx.hostpython)
|
||||||
|
if not self.ctx.python_recipe.from_crystax:
|
||||||
|
try:
|
||||||
|
shprint(hostpython, '-OO', '-m', 'compileall',
|
||||||
|
self.ctx.get_python_install_dir(),
|
||||||
|
_tail=10, _filterout="^Listing")
|
||||||
|
except sh.ErrorReturnCode:
|
||||||
|
pass
|
||||||
|
if not exists('python-install'):
|
||||||
|
shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install')
|
||||||
|
|
||||||
|
self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
|
||||||
|
self.distribute_aars(arch)
|
||||||
|
self.distribute_javaclasses(self.ctx.javaclass_dir)
|
||||||
|
|
||||||
|
if not self.ctx.python_recipe.from_crystax:
|
||||||
|
info('Filling private directory')
|
||||||
|
if not exists(join('private', 'lib')):
|
||||||
|
info('private/lib does not exist, making')
|
||||||
|
shprint(sh.cp, '-a', join('python-install', 'lib'), 'private')
|
||||||
|
shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7'))
|
||||||
|
|
||||||
|
# AND: Copylibs stuff should go here
|
||||||
|
if exists(join('libs', arch.arch, 'libpymodules.so')):
|
||||||
|
shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/')
|
||||||
|
shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/'))
|
||||||
|
|
||||||
|
info('Removing some unwanted files')
|
||||||
|
shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
|
||||||
|
shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
|
||||||
|
|
||||||
|
libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
|
||||||
|
site_packages_dir = join(libdir, 'site-packages')
|
||||||
|
with current_directory(libdir):
|
||||||
|
# shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.')))
|
||||||
|
removes = []
|
||||||
|
for dirname, something, filens in walk('.'):
|
||||||
|
for filename in filens:
|
||||||
|
for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'):
|
||||||
|
if filename.endswith(suffix):
|
||||||
|
removes.append(filename)
|
||||||
|
shprint(sh.rm, '-f', *removes)
|
||||||
|
|
||||||
|
info('Deleting some other stuff not used on android')
|
||||||
|
# To quote the original distribute.sh, 'well...'
|
||||||
|
# shprint(sh.rm, '-rf', 'ctypes')
|
||||||
|
shprint(sh.rm, '-rf', 'lib2to3')
|
||||||
|
shprint(sh.rm, '-rf', 'idlelib')
|
||||||
|
for filename in glob.glob('config/libpython*.a'):
|
||||||
|
shprint(sh.rm, '-f', filename)
|
||||||
|
shprint(sh.rm, '-rf', 'config/python.o')
|
||||||
|
# shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so')
|
||||||
|
# shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so')
|
||||||
|
|
||||||
|
else: # Python *is* loaded from crystax
|
||||||
|
ndk_dir = self.ctx.ndk_dir
|
||||||
|
py_recipe = self.ctx.python_recipe
|
||||||
|
python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version,
|
||||||
|
'libs', arch.arch)
|
||||||
|
|
||||||
|
shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python')
|
||||||
|
shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python')
|
||||||
|
shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages')
|
||||||
|
|
||||||
|
info('Renaming .so files to reflect cross-compile')
|
||||||
|
site_packages_dir = 'crystax_python/crystax_python/site-packages'
|
||||||
|
filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode(
|
||||||
|
'utf-8').split('\n')[:-1]
|
||||||
|
for filen in filens:
|
||||||
|
parts = filen.split('.')
|
||||||
|
if len(parts) <= 2:
|
||||||
|
continue
|
||||||
|
shprint(sh.mv, filen, filen.split('.')[0] + '.so')
|
||||||
|
site_packages_dir = join(abspath(curdir),
|
||||||
|
site_packages_dir)
|
||||||
|
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()
|
18
p4a/pythonforandroid/bootstraps/lbry/build/ant.properties
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# This file is used to override default values used by the Ant build system.
|
||||||
|
#
|
||||||
|
# This file must be checked into Version Control Systems, as it is
|
||||||
|
# integral to the build system of your project.
|
||||||
|
|
||||||
|
# This file is only used by the Ant script.
|
||||||
|
|
||||||
|
# You can use this to override default values such as
|
||||||
|
# 'source.dir' for the location of your java source folder and
|
||||||
|
# 'out.dir' for the location of your output folder.
|
||||||
|
|
||||||
|
# You can also use it define how the release builds are signed by declaring
|
||||||
|
# the following properties:
|
||||||
|
# 'key.store' for the location of your keystore and
|
||||||
|
# 'key.alias' for the name of the key to use.
|
||||||
|
# The password will be asked during the build when you use the 'release' target.
|
||||||
|
|
||||||
|
source.absolute.dir = tmp-src
|
83
p4a/pythonforandroid/bootstraps/lbry/build/blacklist.txt
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# prevent user to include invalid extensions
|
||||||
|
*.apk
|
||||||
|
*.pxd
|
||||||
|
|
||||||
|
# eggs
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# unit test
|
||||||
|
unittest/*
|
||||||
|
|
||||||
|
# python config
|
||||||
|
config/makesetup
|
||||||
|
|
||||||
|
# unused kivy files (platform specific)
|
||||||
|
kivy/input/providers/wm_*
|
||||||
|
kivy/input/providers/mactouch*
|
||||||
|
kivy/input/providers/probesysfs*
|
||||||
|
kivy/input/providers/mtdev*
|
||||||
|
kivy/input/providers/hidinput*
|
||||||
|
kivy/core/camera/camera_videocapture*
|
||||||
|
kivy/core/spelling/*osx*
|
||||||
|
kivy/core/video/video_pyglet*
|
||||||
|
kivy/tools
|
||||||
|
kivy/tests/*
|
||||||
|
kivy/*/*.h
|
||||||
|
kivy/*/*.pxi
|
||||||
|
|
||||||
|
# unused encodings
|
||||||
|
lib-dynload/*codec*
|
||||||
|
encodings/cp*.pyo
|
||||||
|
encodings/tis*
|
||||||
|
encodings/shift*
|
||||||
|
encodings/bz2*
|
||||||
|
encodings/iso*
|
||||||
|
encodings/undefined*
|
||||||
|
encodings/johab*
|
||||||
|
encodings/p*
|
||||||
|
encodings/m*
|
||||||
|
encodings/euc*
|
||||||
|
encodings/k*
|
||||||
|
encodings/unicode_internal*
|
||||||
|
encodings/quo*
|
||||||
|
encodings/gb*
|
||||||
|
encodings/big5*
|
||||||
|
encodings/hp*
|
||||||
|
encodings/hz*
|
||||||
|
|
||||||
|
# unused python modules
|
||||||
|
bsddb/*
|
||||||
|
wsgiref/*
|
||||||
|
hotshot/*
|
||||||
|
pydoc_data/*
|
||||||
|
tty.pyo
|
||||||
|
anydbm.pyo
|
||||||
|
nturl2path.pyo
|
||||||
|
LICENCE.txt
|
||||||
|
macurl2path.pyo
|
||||||
|
dummy_threading.pyo
|
||||||
|
audiodev.pyo
|
||||||
|
antigravity.pyo
|
||||||
|
dumbdbm.pyo
|
||||||
|
sndhdr.pyo
|
||||||
|
__phello__.foo.pyo
|
||||||
|
sunaudio.pyo
|
||||||
|
os2emxpath.pyo
|
||||||
|
multiprocessing/dummy*
|
||||||
|
|
||||||
|
# unused binaries python modules
|
||||||
|
lib-dynload/termios.so
|
||||||
|
lib-dynload/_lsprof.so
|
||||||
|
lib-dynload/*audioop.so
|
||||||
|
lib-dynload/mmap.so
|
||||||
|
lib-dynload/_hotshot.so
|
||||||
|
lib-dynload/_heapq.so
|
||||||
|
lib-dynload/_json.so
|
||||||
|
lib-dynload/grp.so
|
||||||
|
lib-dynload/resource.so
|
||||||
|
lib-dynload/pyexpat.so
|
||||||
|
lib-dynload/_ctypes_test.so
|
||||||
|
lib-dynload/_testcapi.so
|
||||||
|
|
||||||
|
# odd files
|
||||||
|
plat-linux3/regen
|
566
p4a/pythonforandroid/bootstraps/lbry/build/build.py
Executable file
|
@ -0,0 +1,566 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from os.path import dirname, join, isfile, realpath, relpath, split, exists
|
||||||
|
from os import makedirs, remove
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
from zipfile import ZipFile
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
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.
|
||||||
|
PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
|
||||||
|
if not exists(PYTHON):
|
||||||
|
print('Could not find hostpython, will not compile to .pyo (this is normal with python3)')
|
||||||
|
PYTHON = None
|
||||||
|
|
||||||
|
BLACKLIST_PATTERNS = [
|
||||||
|
# code versionning
|
||||||
|
'^*.hg/*',
|
||||||
|
'^*.git/*',
|
||||||
|
'^*.bzr/*',
|
||||||
|
'^*.svn/*',
|
||||||
|
|
||||||
|
# pyc/py
|
||||||
|
'*.pyc',
|
||||||
|
|
||||||
|
# temp files
|
||||||
|
'~',
|
||||||
|
'*.bak',
|
||||||
|
'*.swp',
|
||||||
|
]
|
||||||
|
if PYTHON is not None:
|
||||||
|
BLACKLIST_PATTERNS.append('*.py')
|
||||||
|
|
||||||
|
WHITELIST_PATTERNS = ['pyconfig.h', ]
|
||||||
|
|
||||||
|
python_files = []
|
||||||
|
|
||||||
|
|
||||||
|
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
|
||||||
|
join(curdir, 'templates')))
|
||||||
|
|
||||||
|
def render(template, dest, **kwargs):
|
||||||
|
'''Using jinja2, render `template` to the filename `dest`, supplying the
|
||||||
|
|
||||||
|
keyword arguments as template parameters.
|
||||||
|
'''
|
||||||
|
|
||||||
|
dest_dir = dirname(dest)
|
||||||
|
if dest_dir and not exists(dest_dir):
|
||||||
|
makedirs(dest_dir)
|
||||||
|
|
||||||
|
template = environment.get_template(template)
|
||||||
|
text = template.render(**kwargs)
|
||||||
|
|
||||||
|
f = open(dest, 'wb')
|
||||||
|
f.write(text.encode('utf-8'))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def is_whitelist(name):
|
||||||
|
return match_filename(WHITELIST_PATTERNS, name)
|
||||||
|
|
||||||
|
|
||||||
|
def is_blacklist(name):
|
||||||
|
if is_whitelist(name):
|
||||||
|
return False
|
||||||
|
return match_filename(BLACKLIST_PATTERNS, name)
|
||||||
|
|
||||||
|
|
||||||
|
def match_filename(pattern_list, name):
|
||||||
|
for pattern in pattern_list:
|
||||||
|
if pattern.startswith('^'):
|
||||||
|
pattern = pattern[1:]
|
||||||
|
else:
|
||||||
|
pattern = '*/' + pattern
|
||||||
|
if fnmatch(name, pattern):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def listfiles(d):
|
||||||
|
basedir = d
|
||||||
|
subdirlist = []
|
||||||
|
for item in os.listdir(d):
|
||||||
|
fn = join(d, item)
|
||||||
|
if isfile(fn):
|
||||||
|
yield fn
|
||||||
|
else:
|
||||||
|
subdirlist.append(join(basedir, item))
|
||||||
|
for subdir in subdirlist:
|
||||||
|
for fn in listfiles(subdir):
|
||||||
|
yield fn
|
||||||
|
|
||||||
|
def make_python_zip():
|
||||||
|
'''
|
||||||
|
Search for all the python related files, and construct the pythonXX.zip
|
||||||
|
According to
|
||||||
|
# http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
|
||||||
|
site-packages, config and lib-dynload will be not included.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not exists('private'):
|
||||||
|
print('No compiled python is present to zip, skipping.')
|
||||||
|
print('this should only be the case if you are using the CrystaX python')
|
||||||
|
return
|
||||||
|
|
||||||
|
global python_files
|
||||||
|
d = realpath(join('private', 'lib', 'python2.7'))
|
||||||
|
|
||||||
|
|
||||||
|
def select(fn):
|
||||||
|
if is_blacklist(fn):
|
||||||
|
return False
|
||||||
|
fn = realpath(fn)
|
||||||
|
assert(fn.startswith(d))
|
||||||
|
fn = fn[len(d):]
|
||||||
|
if (fn.startswith('/site-packages/') or
|
||||||
|
fn.startswith('/config/') or
|
||||||
|
fn.startswith('/lib-dynload/') or
|
||||||
|
fn.startswith('/libpymodules.so')):
|
||||||
|
return False
|
||||||
|
return fn
|
||||||
|
|
||||||
|
# get a list of all python file
|
||||||
|
python_files = [x for x in listfiles(d) if select(x)]
|
||||||
|
|
||||||
|
# create the final zipfile
|
||||||
|
zfn = join('private', 'lib', 'python27.zip')
|
||||||
|
zf = ZipFile(zfn, 'w')
|
||||||
|
|
||||||
|
# put all the python files in it
|
||||||
|
for fn in python_files:
|
||||||
|
afn = fn[len(d):]
|
||||||
|
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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# selector function
|
||||||
|
def select(fn):
|
||||||
|
rfn = realpath(fn)
|
||||||
|
for p in ignore_path:
|
||||||
|
if p.endswith('/'):
|
||||||
|
p = p[:-1]
|
||||||
|
if rfn.startswith(p):
|
||||||
|
return False
|
||||||
|
if rfn in python_files:
|
||||||
|
return False
|
||||||
|
return not is_blacklist(fn)
|
||||||
|
|
||||||
|
# get the files and relpath file of all the directory we asked for
|
||||||
|
files = []
|
||||||
|
for sd in source_dirs:
|
||||||
|
sd = realpath(sd)
|
||||||
|
compile_dir(sd)
|
||||||
|
files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
|
||||||
|
if select(x)]
|
||||||
|
|
||||||
|
# create tar.gz of thoses files
|
||||||
|
tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
|
||||||
|
dirs = []
|
||||||
|
for fn, afn in files:
|
||||||
|
# print('%s: %s' % (tfn, fn))
|
||||||
|
dn = dirname(afn)
|
||||||
|
if dn not in dirs:
|
||||||
|
# create every dirs first if not exist yet
|
||||||
|
d = ''
|
||||||
|
for component in split(dn):
|
||||||
|
d = join(d, component)
|
||||||
|
if d.startswith('/'):
|
||||||
|
d = d[1:]
|
||||||
|
if d == '' or d in dirs:
|
||||||
|
continue
|
||||||
|
dirs.append(d)
|
||||||
|
tinfo = tarfile.TarInfo(d)
|
||||||
|
tinfo.type = tarfile.DIRTYPE
|
||||||
|
tf.addfile(tinfo)
|
||||||
|
|
||||||
|
# put the file
|
||||||
|
tf.add(fn, afn)
|
||||||
|
tf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def compile_dir(dfn):
|
||||||
|
'''
|
||||||
|
Compile *.py in directory `dfn` to *.pyo
|
||||||
|
'''
|
||||||
|
# -OO = strip docstrings
|
||||||
|
if PYTHON is None:
|
||||||
|
return
|
||||||
|
subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', 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
|
||||||
|
exists(join(realpath(args.private), 'main.pyo'))):
|
||||||
|
print('''BUILD FAILURE: No main.py(o) found in your app directory. This
|
||||||
|
file must exist to act as the entry point for you app. If your app is
|
||||||
|
started by a file with a different name, rename it to main.py or add a
|
||||||
|
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')
|
||||||
|
|
||||||
|
# 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
|
||||||
|
tar_dirs = [args.private]
|
||||||
|
if exists('private'):
|
||||||
|
tar_dirs.append('private')
|
||||||
|
if exists('crystax_python'):
|
||||||
|
tar_dirs.append('crystax_python')
|
||||||
|
|
||||||
|
if args.private:
|
||||||
|
make_tar('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)
|
||||||
|
|
||||||
|
|
||||||
|
# folder name for launcher
|
||||||
|
url_scheme = 'kivy'
|
||||||
|
|
||||||
|
# Prepare some variables for templating process
|
||||||
|
if args.launcher:
|
||||||
|
default_icon = 'templates/launcher-icon.png'
|
||||||
|
default_presplash = 'templates/launcher-presplash.jpg'
|
||||||
|
else:
|
||||||
|
default_icon = 'templates/kivy-icon.png'
|
||||||
|
default_presplash = 'templates/kivy-presplash.jpg'
|
||||||
|
shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
|
||||||
|
|
||||||
|
shutil.copy(args.presplash or default_presplash,
|
||||||
|
'res/drawable/presplash.jpg')
|
||||||
|
|
||||||
|
# If extra Java jars were requested, copy them into the libs directory
|
||||||
|
if args.add_jar:
|
||||||
|
for jarname in args.add_jar:
|
||||||
|
if not exists(jarname):
|
||||||
|
print('Requested jar does not exist: {}'.format(jarname))
|
||||||
|
sys.exit(-1)
|
||||||
|
shutil.copy(jarname, 'libs')
|
||||||
|
|
||||||
|
versioned_name = (args.name.replace(' ', '').replace('\'', '') +
|
||||||
|
'-' + args.version)
|
||||||
|
|
||||||
|
version_code = 0
|
||||||
|
if not args.numeric_version:
|
||||||
|
for i in args.version.split('.'):
|
||||||
|
version_code *= 100
|
||||||
|
version_code += int(i)
|
||||||
|
args.numeric_version = str(version_code)
|
||||||
|
|
||||||
|
if args.intent_filters:
|
||||||
|
with open(args.intent_filters) as fd:
|
||||||
|
args.intent_filters = fd.read()
|
||||||
|
|
||||||
|
if args.extra_source_dirs:
|
||||||
|
esd = []
|
||||||
|
for spec in args.extra_source_dirs:
|
||||||
|
if ':' in spec:
|
||||||
|
specdir, specincludes = spec.split(':')
|
||||||
|
else:
|
||||||
|
specdir = spec
|
||||||
|
specincludes = '**'
|
||||||
|
esd.append((realpath(specdir), specincludes))
|
||||||
|
args.extra_source_dirs = esd
|
||||||
|
else:
|
||||||
|
args.extra_source_dirs = []
|
||||||
|
|
||||||
|
service = False
|
||||||
|
if args.private:
|
||||||
|
service_main = join(realpath(args.private), 'service', 'main.py')
|
||||||
|
if exists(service_main) or exists(service_main + 'o'):
|
||||||
|
service = True
|
||||||
|
|
||||||
|
service_names = []
|
||||||
|
for sid, spec in enumerate(args.services):
|
||||||
|
spec = spec.split(':')
|
||||||
|
name = spec[0]
|
||||||
|
entrypoint = spec[1]
|
||||||
|
options = spec[2:]
|
||||||
|
|
||||||
|
foreground = 'foreground' in options
|
||||||
|
sticky = 'sticky' in options
|
||||||
|
|
||||||
|
service_names.append(name)
|
||||||
|
render(
|
||||||
|
'Service.tmpl.java',
|
||||||
|
'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
|
||||||
|
name=name,
|
||||||
|
entrypoint=entrypoint,
|
||||||
|
args=args,
|
||||||
|
foreground=foreground,
|
||||||
|
sticky=sticky,
|
||||||
|
service_id=sid + 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
render(
|
||||||
|
'AndroidManifest.tmpl.xml',
|
||||||
|
'AndroidManifest.xml',
|
||||||
|
args=args,
|
||||||
|
service=service,
|
||||||
|
service_names=service_names,
|
||||||
|
url_scheme=url_scheme,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# add colors.xml
|
||||||
|
render(
|
||||||
|
'colors.tmpl.xml',
|
||||||
|
'res/values/colors.xml',
|
||||||
|
args=args,
|
||||||
|
url_scheme=url_scheme,
|
||||||
|
)
|
||||||
|
|
||||||
|
# add activity_service_control
|
||||||
|
render(
|
||||||
|
'activity_service_control.xml',
|
||||||
|
'res/layout/activity_service_control.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
|
||||||
|
import argparse
|
||||||
|
ap = argparse.ArgumentParser(description='''\
|
||||||
|
Package a Python application for Android.
|
||||||
|
|
||||||
|
For this to work, Java and Ant need to be in your path, as does the
|
||||||
|
tools directory of the Android SDK.
|
||||||
|
''')
|
||||||
|
|
||||||
|
ap.add_argument('--private', dest='private',
|
||||||
|
help='the dir of user files')
|
||||||
|
# , required=True) for launcher, crashes in make_package
|
||||||
|
# if not mentioned (and the check is there anyway)
|
||||||
|
ap.add_argument('--package', dest='package',
|
||||||
|
help=('The name of the java package the project will be'
|
||||||
|
' packaged under.'),
|
||||||
|
required=True)
|
||||||
|
ap.add_argument('--name', dest='name',
|
||||||
|
help=('The human-readable name of the project.'),
|
||||||
|
required=True)
|
||||||
|
ap.add_argument('--numeric-version', dest='numeric_version',
|
||||||
|
help=('The numeric version number of the project. If not '
|
||||||
|
'given, this is automatically computed from the '
|
||||||
|
'version.'))
|
||||||
|
ap.add_argument('--version', dest='version',
|
||||||
|
help=('The version number of the project. This should '
|
||||||
|
'consist of numbers and dots, and should have the '
|
||||||
|
'same number of groups of numbers as previous '
|
||||||
|
'versions.'),
|
||||||
|
required=True)
|
||||||
|
ap.add_argument('--orientation', dest='orientation', default='portrait',
|
||||||
|
help=('The orientation that the game will display in. '
|
||||||
|
'Usually one of "landscape", "portrait", '
|
||||||
|
'"sensor", or "user" (the same as "sensor" but '
|
||||||
|
'obeying the user\'s Android rotation setting). '
|
||||||
|
'The full list of options is given under '
|
||||||
|
'android_screenOrientation at '
|
||||||
|
'https://developer.android.com/guide/topics/manifest/'
|
||||||
|
'activity-element.html'))
|
||||||
|
ap.add_argument('--launcher', dest='launcher', action='store_true',
|
||||||
|
help=('Provide this argument to build a multi-app '
|
||||||
|
'launcher, rather than a single app.'))
|
||||||
|
ap.add_argument('--icon', dest='icon',
|
||||||
|
help='A png file to use as the icon for the application.')
|
||||||
|
ap.add_argument('--permission', dest='permissions', action='append',
|
||||||
|
help='The permissions to give this app.', nargs='+')
|
||||||
|
ap.add_argument('--meta-data', dest='meta_data', action='append',
|
||||||
|
help='Custom key=value to add in application metadata')
|
||||||
|
ap.add_argument('--presplash', dest='presplash',
|
||||||
|
help=('A jpeg file to use as a screen while the '
|
||||||
|
'application is loading.'))
|
||||||
|
ap.add_argument('--presplash-color', dest='presplash_color', default='#000000',
|
||||||
|
help=('A string to set the loading screen background color. '
|
||||||
|
'Supported formats are: #RRGGBB #AARRGGBB or color names '
|
||||||
|
'like red, green, blue, etc.'))
|
||||||
|
ap.add_argument('--wakelock', dest='wakelock', action='store_true',
|
||||||
|
help=('Indicate if the application needs the device '
|
||||||
|
'to stay on'))
|
||||||
|
ap.add_argument('--window', dest='window', action='store_true',
|
||||||
|
help='Indicate if the application will be windowed')
|
||||||
|
ap.add_argument('--blacklist', dest='blacklist',
|
||||||
|
default=join(curdir, 'blacklist.txt'),
|
||||||
|
help=('Use a blacklist file to match unwanted file in '
|
||||||
|
'the final APK'))
|
||||||
|
ap.add_argument('--whitelist', dest='whitelist',
|
||||||
|
default=join(curdir, 'whitelist.txt'),
|
||||||
|
help=('Use a whitelist file to prevent blacklisting of '
|
||||||
|
'file in the final APK'))
|
||||||
|
ap.add_argument('--add-jar', dest='add_jar', action='append',
|
||||||
|
help=('Add a Java .jar to the libs, so you can access its '
|
||||||
|
'classes with pyjnius. You can specify this '
|
||||||
|
'argument more than once to include multiple jars'))
|
||||||
|
ap.add_argument('--sdk', dest='sdk_version', default=-1,
|
||||||
|
type=int, help=('Android SDK version to use. Default to '
|
||||||
|
'the value of minsdk'))
|
||||||
|
ap.add_argument('--minsdk', dest='min_sdk_version',
|
||||||
|
default=default_android_api, type=int,
|
||||||
|
help=('Minimum Android SDK version to use. Default to '
|
||||||
|
'the value of ANDROIDAPI, or {} if not set'
|
||||||
|
.format(default_android_api)))
|
||||||
|
ap.add_argument('--intent-filters', dest='intent_filters',
|
||||||
|
help=('Add intent-filters xml rules to the '
|
||||||
|
'AndroidManifest.xml file. The argument is a '
|
||||||
|
'filename containing xml. The filename should be '
|
||||||
|
'located relative to the python-for-android '
|
||||||
|
'directory'))
|
||||||
|
ap.add_argument('--with-billing', dest='billing_pubkey',
|
||||||
|
help='If set, the billing service will be added (not implemented)')
|
||||||
|
ap.add_argument('--service', dest='services', action='append',
|
||||||
|
help='Declare a new service entrypoint: '
|
||||||
|
'NAME:PATH_TO_PY[:foreground]')
|
||||||
|
ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
|
||||||
|
help='Include additional source dirs in Java build')
|
||||||
|
ap.add_argument('--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',
|
||||||
|
help=('Try to sign the APK with your credentials. You must set '
|
||||||
|
'the appropriate environment variables.'))
|
||||||
|
|
||||||
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
args = ap.parse_args(args)
|
||||||
|
args.ignore_path = []
|
||||||
|
|
||||||
|
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.permissions is None:
|
||||||
|
args.permissions = []
|
||||||
|
elif args.permissions:
|
||||||
|
if isinstance(args.permissions[0], list):
|
||||||
|
args.permissions = [p for perm in args.permissions for p in perm]
|
||||||
|
|
||||||
|
if args.meta_data is None:
|
||||||
|
args.meta_data = []
|
||||||
|
|
||||||
|
if args.services is None:
|
||||||
|
args.services = []
|
||||||
|
|
||||||
|
if args.no_compile_pyo:
|
||||||
|
PYTHON = None
|
||||||
|
BLACKLIST_PATTERNS.remove('*.py')
|
||||||
|
|
||||||
|
if args.blacklist:
|
||||||
|
with open(args.blacklist) as fd:
|
||||||
|
patterns = [x.strip() for x in fd.read().splitlines()
|
||||||
|
if x.strip() and not x.strip().startswith('#')]
|
||||||
|
BLACKLIST_PATTERNS += patterns
|
||||||
|
|
||||||
|
if args.whitelist:
|
||||||
|
with open(args.whitelist) as fd:
|
||||||
|
patterns = [x.strip() for x in fd.read().splitlines()
|
||||||
|
if x.strip() and not x.strip().startswith('#')]
|
||||||
|
WHITELIST_PATTERNS += patterns
|
||||||
|
|
||||||
|
make_package(args)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
parse_args()
|
93
p4a/pythonforandroid/bootstraps/lbry/build/build.xml
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This should be changed to the name of your project -->
|
||||||
|
<project name="SDLActivity" default="help">
|
||||||
|
|
||||||
|
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||||
|
It contains the path to the SDK. It should *NOT* be checked into
|
||||||
|
Version Control Systems. -->
|
||||||
|
<property file="local.properties" />
|
||||||
|
|
||||||
|
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||||
|
'android' tool to add properties to it.
|
||||||
|
This is the place to change some Ant specific build properties.
|
||||||
|
Here are some properties you may want to change/update:
|
||||||
|
|
||||||
|
source.dir
|
||||||
|
The name of the source directory. Default is 'src'.
|
||||||
|
out.dir
|
||||||
|
The name of the output directory. Default is 'bin'.
|
||||||
|
|
||||||
|
For other overridable properties, look at the beginning of the rules
|
||||||
|
files in the SDK, at tools/ant/build.xml
|
||||||
|
|
||||||
|
Properties related to the SDK location or the project target should
|
||||||
|
be updated using the 'android' tool with the 'update' action.
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<property file="ant.properties" />
|
||||||
|
|
||||||
|
<!-- if sdk.dir was not set from one of the property file, then
|
||||||
|
get it from the ANDROID_HOME env var.
|
||||||
|
This must be done before we load project.properties since
|
||||||
|
the proguard config can use sdk.dir -->
|
||||||
|
<property environment="env" />
|
||||||
|
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||||
|
<isset property="env.ANDROID_HOME" />
|
||||||
|
</condition>
|
||||||
|
|
||||||
|
<!-- The project.properties file is created and updated by the 'android'
|
||||||
|
tool, as well as ADT.
|
||||||
|
|
||||||
|
This contains project specific properties such as project target, and library
|
||||||
|
dependencies. Lower level build properties are stored in ant.properties
|
||||||
|
(or in .classpath for Eclipse projects).
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems. -->
|
||||||
|
<loadproperties srcFile="project.properties" />
|
||||||
|
|
||||||
|
<!-- quick check on sdk.dir -->
|
||||||
|
<fail
|
||||||
|
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||||
|
unless="sdk.dir"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Import per project custom build rules if present at the root of the project.
|
||||||
|
This is the place to put custom intermediary targets such as:
|
||||||
|
-pre-build
|
||||||
|
-pre-compile
|
||||||
|
-post-compile (This is typically used for code obfuscation.
|
||||||
|
Compiled code location: ${out.classes.absolute.dir}
|
||||||
|
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||||
|
-post-package
|
||||||
|
-post-build
|
||||||
|
-pre-clean
|
||||||
|
-->
|
||||||
|
<import file="custom_rules.xml" optional="true" />
|
||||||
|
|
||||||
|
<!-- Import the actual build file.
|
||||||
|
|
||||||
|
To customize existing targets, there are two options:
|
||||||
|
- Customize only one target:
|
||||||
|
- copy/paste the target into this file, *before* the
|
||||||
|
<import> task.
|
||||||
|
- customize it to your needs.
|
||||||
|
- Customize the whole content of build.xml
|
||||||
|
- copy/paste the content of the rules files (minus the top node)
|
||||||
|
into this file, replacing the <import> task.
|
||||||
|
- customize to your needs.
|
||||||
|
|
||||||
|
***********************
|
||||||
|
****** IMPORTANT ******
|
||||||
|
***********************
|
||||||
|
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||||
|
in order to avoid having your file be overridden by tools such as "android update project"
|
||||||
|
-->
|
||||||
|
<!-- version-tag: 1 -->
|
||||||
|
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1 @@
|
||||||
|
include $(call all-subdir-makefiles)
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
# Uncomment this if you're using STL in your project
|
||||||
|
# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
|
||||||
|
# APP_STL := stlport_static
|
||||||
|
|
||||||
|
# APP_ABI := armeabi armeabi-v7a x86
|
||||||
|
APP_ABI := $(ARCH)
|
|
@ -0,0 +1,27 @@
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := main
|
||||||
|
|
||||||
|
SDL_PATH := ../SDL
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
|
||||||
|
|
||||||
|
# Add your application source files here...
|
||||||
|
LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
|
||||||
|
start.c
|
||||||
|
|
||||||
|
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS)
|
||||||
|
|
||||||
|
LOCAL_SHARED_LIBRARIES := SDL2 python_shared
|
||||||
|
|
||||||
|
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)
|
||||||
|
|
||||||
|
LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
ifdef CRYSTAX_PYTHON_VERSION
|
||||||
|
$(call import-module,python/$(CRYSTAX_PYTHON_VERSION))
|
||||||
|
endif
|
|
@ -0,0 +1,12 @@
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := main
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := YourSourceHere.c
|
||||||
|
|
||||||
|
LOCAL_STATIC_LIBRARIES := SDL2_static
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
$(call import-module,SDL)LOCAL_PATH := $(call my-dir)
|
320
p4a/pythonforandroid/bootstraps/lbry/build/jni/src/start.c
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
|
||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
#include "Python.h"
|
||||||
|
#ifndef Py_PYTHON_H
|
||||||
|
#error Python headers needed to compile C extensions, please install development version of Python.
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
#include "android/log.h"
|
||||||
|
#include "SDL_opengles2.h"
|
||||||
|
|
||||||
|
#define ENTRYPOINT_MAXLEN 128
|
||||||
|
#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))
|
||||||
|
#define LOGP(x) LOG("python", (x))
|
||||||
|
|
||||||
|
static PyObject *androidembed_log(PyObject *self, PyObject *args) {
|
||||||
|
char *logstr = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &logstr)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LOG(getenv("PYTHON_NAME"), logstr);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef AndroidEmbedMethods[] = {
|
||||||
|
{"log", androidembed_log, METH_VARARGS, "Log on android platform"},
|
||||||
|
{NULL, NULL, 0, NULL}};
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
static struct PyModuleDef androidembed = {PyModuleDef_HEAD_INIT, "androidembed",
|
||||||
|
"", -1, AndroidEmbedMethods};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC initandroidembed(void) {
|
||||||
|
return PyModule_Create(&androidembed);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PyMODINIT_FUNC initandroidembed(void) {
|
||||||
|
(void)Py_InitModule("androidembed", AndroidEmbedMethods);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int dir_exists(char *filename) {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(filename, &st) == 0) {
|
||||||
|
if (S_ISDIR(st.st_mode))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int file_exists(const char *filename) {
|
||||||
|
FILE *file;
|
||||||
|
if (file = fopen(filename, "r")) {
|
||||||
|
fclose(file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* int main(int argc, char **argv) { */
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
char *env_argument = NULL;
|
||||||
|
char *env_entrypoint = NULL;
|
||||||
|
char *env_logname = NULL;
|
||||||
|
char entrypoint[ENTRYPOINT_MAXLEN];
|
||||||
|
int ret = 0;
|
||||||
|
FILE *fd;
|
||||||
|
|
||||||
|
/* AND: Several filepaths are hardcoded here, these must be made
|
||||||
|
configurable */
|
||||||
|
/* AND: P4A uses env vars...not sure what's best */
|
||||||
|
LOGP("Initialize Python for Android");
|
||||||
|
env_argument = getenv("ANDROID_ARGUMENT");
|
||||||
|
setenv("ANDROID_APP_PATH", env_argument, 1);
|
||||||
|
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
|
||||||
|
env_logname = getenv("PYTHON_NAME");
|
||||||
|
|
||||||
|
if (env_logname == NULL) {
|
||||||
|
env_logname = "python";
|
||||||
|
setenv("PYTHON_NAME", "python", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
|
||||||
|
LOGP(env_argument);
|
||||||
|
chdir(env_argument);
|
||||||
|
|
||||||
|
Py_SetProgramName(L"android_python");
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
/* our logging module for android
|
||||||
|
*/
|
||||||
|
PyImport_AppendInittab("androidembed", initandroidembed);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGP("Preparing to initialize python");
|
||||||
|
|
||||||
|
if (dir_exists("crystax_python/")) {
|
||||||
|
LOGP("crystax_python exists");
|
||||||
|
char paths[256];
|
||||||
|
snprintf(paths, 256,
|
||||||
|
"%s/crystax_python/stdlib.zip:%s/crystax_python/modules",
|
||||||
|
env_argument, env_argument);
|
||||||
|
/* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
|
||||||
|
* env_argument); */
|
||||||
|
LOGP("calculated paths to be...");
|
||||||
|
LOGP(paths);
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
|
||||||
|
Py_SetPath(wchar_paths);
|
||||||
|
#else
|
||||||
|
char *wchar_paths = paths;
|
||||||
|
LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet");
|
||||||
|
exit(1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGP("set wchar paths...");
|
||||||
|
} else {
|
||||||
|
LOGP("crystax_python does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_Initialize();
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
PySys_SetArgv(argc, argv);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGP("Initialized python");
|
||||||
|
|
||||||
|
/* ensure threads will work.
|
||||||
|
*/
|
||||||
|
LOGP("AND: Init threads");
|
||||||
|
PyEval_InitThreads();
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
initandroidembed();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PyRun_SimpleString("import androidembed\nandroidembed.log('testing python "
|
||||||
|
"print redirection')");
|
||||||
|
|
||||||
|
/* inject our bootstrap code to redirect python stdin/stdout
|
||||||
|
* replace sys.path with our path
|
||||||
|
*/
|
||||||
|
PyRun_SimpleString("import sys, posix\n");
|
||||||
|
if (dir_exists("lib")) {
|
||||||
|
/* If we built our own python, set up the paths correctly */
|
||||||
|
LOGP("Setting up python from ANDROID_PRIVATE");
|
||||||
|
PyRun_SimpleString("private = posix.environ['ANDROID_APP_PATH']\n"
|
||||||
|
"argument = posix.environ['ANDROID_ARGUMENT']\n"
|
||||||
|
"sys.path[:] = [ \n"
|
||||||
|
" private + '/lib/python27.zip', \n"
|
||||||
|
" private + '/lib/python2.7/', \n"
|
||||||
|
" private + '/lib/python2.7/lib-dynload/', \n"
|
||||||
|
" private + '/lib/python2.7/site-packages/', \n"
|
||||||
|
" argument ]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir_exists("crystax_python")) {
|
||||||
|
char add_site_packages_dir[256];
|
||||||
|
snprintf(add_site_packages_dir, 256,
|
||||||
|
"sys.path.append('%s/crystax_python/site-packages')",
|
||||||
|
env_argument);
|
||||||
|
|
||||||
|
PyRun_SimpleString("import sys\n"
|
||||||
|
"sys.argv = ['notaninterpreterreally']\n"
|
||||||
|
"from os.path import realpath, join, dirname");
|
||||||
|
PyRun_SimpleString(add_site_packages_dir);
|
||||||
|
/* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
|
||||||
|
PyRun_SimpleString("sys.path = ['.'] + sys.path");
|
||||||
|
}
|
||||||
|
|
||||||
|
PyRun_SimpleString(
|
||||||
|
"class LogFile(object):\n"
|
||||||
|
" def __init__(self):\n"
|
||||||
|
" self.buffer = ''\n"
|
||||||
|
" def write(self, s):\n"
|
||||||
|
" s = self.buffer + s\n"
|
||||||
|
" lines = s.split(\"\\n\")\n"
|
||||||
|
" for l in lines[:-1]:\n"
|
||||||
|
" androidembed.log(l)\n"
|
||||||
|
" self.buffer = lines[-1]\n"
|
||||||
|
" def flush(self):\n"
|
||||||
|
" return\n"
|
||||||
|
"sys.stdout = sys.stderr = LogFile()\n"
|
||||||
|
"print('Android path', sys.path)\n"
|
||||||
|
"import os\n"
|
||||||
|
"print('os.environ is', os.environ)\n"
|
||||||
|
"print('Android kivy bootstrap done. __name__ is', __name__)");
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
PyRun_SimpleString("import site; print site.getsitepackages()\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGP("AND: Ran string");
|
||||||
|
|
||||||
|
/* run it !
|
||||||
|
*/
|
||||||
|
LOGP("Run user program, change dir and execute entrypoint");
|
||||||
|
|
||||||
|
/* Get the entrypoint, search the .pyo then .py
|
||||||
|
*/
|
||||||
|
char *dot = strrchr(env_entrypoint, '.');
|
||||||
|
if (dot <= 0) {
|
||||||
|
LOGP("Invalid entrypoint, abort.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (strlen(env_entrypoint) > ENTRYPOINT_MAXLEN - 2) {
|
||||||
|
LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!strcmp(dot, ".pyo")) {
|
||||||
|
if (!file_exists(env_entrypoint)) {
|
||||||
|
/* fallback on .py */
|
||||||
|
strcpy(entrypoint, env_entrypoint);
|
||||||
|
entrypoint[strlen(env_entrypoint) - 1] = '\0';
|
||||||
|
LOGP(entrypoint);
|
||||||
|
if (!file_exists(entrypoint)) {
|
||||||
|
LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(entrypoint, env_entrypoint);
|
||||||
|
}
|
||||||
|
} else if (!strcmp(dot, ".py")) {
|
||||||
|
/* if .py is passed, check the pyo version first */
|
||||||
|
strcpy(entrypoint, env_entrypoint);
|
||||||
|
entrypoint[strlen(env_entrypoint) + 1] = '\0';
|
||||||
|
entrypoint[strlen(env_entrypoint)] = 'o';
|
||||||
|
if (!file_exists(entrypoint)) {
|
||||||
|
/* fallback on pure python version */
|
||||||
|
if (!file_exists(env_entrypoint)) {
|
||||||
|
LOGP("Entrypoint not found (.py), abort.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strcpy(entrypoint, env_entrypoint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// LOGP("Entrypoint is:");
|
||||||
|
// LOGP(entrypoint);
|
||||||
|
fd = fopen(entrypoint, "r");
|
||||||
|
if (fd == NULL) {
|
||||||
|
LOGP("Open the entrypoint failed");
|
||||||
|
LOGP(entrypoint);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* run python !
|
||||||
|
*/
|
||||||
|
ret = PyRun_SimpleFile(fd, entrypoint);
|
||||||
|
|
||||||
|
if (PyErr_Occurred() != NULL) {
|
||||||
|
ret = 1;
|
||||||
|
PyErr_Print(); /* This exits with the right code if SystemExit. */
|
||||||
|
PyObject *f = PySys_GetObject("stdout");
|
||||||
|
if (PyFile_WriteString(
|
||||||
|
"\n", f)) /* python2 used Py_FlushLine, but this no longer exists */
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* close everything
|
||||||
|
*/
|
||||||
|
Py_Finalize();
|
||||||
|
fclose(fd);
|
||||||
|
|
||||||
|
LOGP("Python for android ended.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
|
||||||
|
JNIEnv *env, jobject thiz, jstring j_android_private,
|
||||||
|
jstring j_android_argument, jstring j_service_entrypoint,
|
||||||
|
jstring j_python_name, jstring j_python_home, jstring j_python_path,
|
||||||
|
jstring j_arg) {
|
||||||
|
jboolean iscopy;
|
||||||
|
const char *android_private =
|
||||||
|
(*env)->GetStringUTFChars(env, j_android_private, &iscopy);
|
||||||
|
const char *android_argument =
|
||||||
|
(*env)->GetStringUTFChars(env, j_android_argument, &iscopy);
|
||||||
|
const char *service_entrypoint =
|
||||||
|
(*env)->GetStringUTFChars(env, j_service_entrypoint, &iscopy);
|
||||||
|
const char *python_name =
|
||||||
|
(*env)->GetStringUTFChars(env, j_python_name, &iscopy);
|
||||||
|
const char *python_home =
|
||||||
|
(*env)->GetStringUTFChars(env, j_python_home, &iscopy);
|
||||||
|
const char *python_path =
|
||||||
|
(*env)->GetStringUTFChars(env, j_python_path, &iscopy);
|
||||||
|
const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy);
|
||||||
|
|
||||||
|
setenv("ANDROID_PRIVATE", android_private, 1);
|
||||||
|
setenv("ANDROID_ARGUMENT", android_argument, 1);
|
||||||
|
setenv("ANDROID_APP_PATH", android_argument, 1);
|
||||||
|
setenv("ANDROID_ENTRYPOINT", service_entrypoint, 1);
|
||||||
|
setenv("PYTHONOPTIMIZE", "2", 1);
|
||||||
|
setenv("PYTHON_NAME", python_name, 1);
|
||||||
|
setenv("PYTHONHOME", python_home, 1);
|
||||||
|
setenv("PYTHONPATH", python_path, 1);
|
||||||
|
setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
|
||||||
|
|
||||||
|
char *argv[] = {"."};
|
||||||
|
/* ANDROID_ARGUMENT points to service subdir,
|
||||||
|
* so main() will run main.py from this dir
|
||||||
|
*/
|
||||||
|
main(1, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,20 @@
|
||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.7 KiB |
BIN
p4a/pythonforandroid/bootstraps/lbry/build/res/drawable/icon.png
Normal file
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="64sp"
|
||||||
|
android:layout_height="64sp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:padding="2sp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:singleLine="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:id="@+id/author"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello World, SDLActivity"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="Please choose a project:"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4sp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/projectList"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emptyText"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4sp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">LbryControl</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>
|
141
p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/Octal.java
Executable 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarConstants {
|
||||||
|
public static final int EOF_BLOCK = 1024;
|
||||||
|
public static final int DATA_BLOCK = 512;
|
||||||
|
public static final int HEADER_BLOCK = 512;
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarEntry {
|
||||||
|
protected File file;
|
||||||
|
protected TarHeader header;
|
||||||
|
|
||||||
|
private TarEntry() {
|
||||||
|
this.file = null;
|
||||||
|
header = new TarHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarEntry(File file, String entryName) {
|
||||||
|
this();
|
||||||
|
this.file = file;
|
||||||
|
this.extractTarHeader(entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarEntry(byte[] headerBuf) {
|
||||||
|
this();
|
||||||
|
this.parseTarHeader(headerBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to create an entry from an existing TarHeader object.
|
||||||
|
*
|
||||||
|
* This method is useful to add new entries programmatically (e.g. for
|
||||||
|
* adding files or directories that do not exist in the file system).
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public TarEntry(TarHeader header) {
|
||||||
|
this.file = null;
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(TarEntry it) {
|
||||||
|
return header.name.toString().equals(it.header.name.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDescendent(TarEntry desc) {
|
||||||
|
return desc.header.name.toString().startsWith(header.name.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarHeader getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
String name = header.name.toString();
|
||||||
|
if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
|
||||||
|
name = header.namePrefix.toString() + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
header.name = new StringBuffer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUserId() {
|
||||||
|
return header.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(int userId) {
|
||||||
|
header.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroupId() {
|
||||||
|
return header.groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(int groupId) {
|
||||||
|
header.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return header.userName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
header.userName = new StringBuffer(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return header.groupName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(String groupName) {
|
||||||
|
header.groupName = new StringBuffer(groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIds(int userId, int groupId) {
|
||||||
|
this.setUserId(userId);
|
||||||
|
this.setGroupId(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModTime(long time) {
|
||||||
|
header.modTime = time / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModTime(Date time) {
|
||||||
|
header.modTime = time.getTime() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModTime() {
|
||||||
|
return new Date(header.modTime * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return this.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return header.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(long size) {
|
||||||
|
header.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the org.kamrazafar.jtar entry is a directory
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isDirectory() {
|
||||||
|
if (this.file != null)
|
||||||
|
return this.file.isDirectory();
|
||||||
|
|
||||||
|
if (header != null) {
|
||||||
|
if (header.linkFlag == TarHeader.LF_DIR)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (header.name.toString().endsWith("/"))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract header from File
|
||||||
|
*
|
||||||
|
* @param entryName
|
||||||
|
*/
|
||||||
|
public void extractTarHeader(String entryName) {
|
||||||
|
header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate checksum
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long computeCheckSum(byte[] buf) {
|
||||||
|
long sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < buf.length; ++i) {
|
||||||
|
sum += 255 & buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the header to the byte buffer
|
||||||
|
*
|
||||||
|
* @param outbuf
|
||||||
|
*/
|
||||||
|
public void writeEntryHeader(byte[] outbuf) {
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
|
||||||
|
offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
|
||||||
|
offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
|
||||||
|
offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
|
||||||
|
|
||||||
|
long size = header.size;
|
||||||
|
|
||||||
|
offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
|
||||||
|
offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
|
||||||
|
|
||||||
|
int csOffset = offset;
|
||||||
|
for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
|
||||||
|
outbuf[offset++] = (byte) ' ';
|
||||||
|
|
||||||
|
outbuf[offset++] = header.linkFlag;
|
||||||
|
|
||||||
|
offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
|
||||||
|
offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
|
||||||
|
offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
|
||||||
|
offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
|
||||||
|
offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
|
||||||
|
offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
|
||||||
|
offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
|
||||||
|
|
||||||
|
for (; offset < outbuf.length;)
|
||||||
|
outbuf[offset++] = 0;
|
||||||
|
|
||||||
|
long checkSum = this.computeCheckSum(outbuf);
|
||||||
|
|
||||||
|
Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the tar header to the byte buffer
|
||||||
|
*
|
||||||
|
* @param header
|
||||||
|
* @param bh
|
||||||
|
*/
|
||||||
|
public void parseTarHeader(byte[] bh) {
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
|
||||||
|
offset += TarHeader.NAMELEN;
|
||||||
|
|
||||||
|
header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
|
||||||
|
offset += TarHeader.MODELEN;
|
||||||
|
|
||||||
|
header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
|
||||||
|
offset += TarHeader.UIDLEN;
|
||||||
|
|
||||||
|
header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
|
||||||
|
offset += TarHeader.GIDLEN;
|
||||||
|
|
||||||
|
header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
|
||||||
|
offset += TarHeader.SIZELEN;
|
||||||
|
|
||||||
|
header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
|
||||||
|
offset += TarHeader.MODTIMELEN;
|
||||||
|
|
||||||
|
header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
|
||||||
|
offset += TarHeader.CHKSUMLEN;
|
||||||
|
|
||||||
|
header.linkFlag = bh[offset++];
|
||||||
|
|
||||||
|
header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
|
||||||
|
offset += TarHeader.NAMELEN;
|
||||||
|
|
||||||
|
header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
|
||||||
|
offset += TarHeader.USTAR_MAGICLEN;
|
||||||
|
|
||||||
|
header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
|
||||||
|
offset += TarHeader.USTAR_USER_NAMELEN;
|
||||||
|
|
||||||
|
header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
|
||||||
|
offset += TarHeader.USTAR_GROUP_NAMELEN;
|
||||||
|
|
||||||
|
header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
|
||||||
|
offset += TarHeader.USTAR_DEVLEN;
|
||||||
|
|
||||||
|
header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
|
||||||
|
offset += TarHeader.USTAR_DEVLEN;
|
||||||
|
|
||||||
|
header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Offset Size Field
|
||||||
|
* 0 100 File name
|
||||||
|
* 100 8 File mode
|
||||||
|
* 108 8 Owner's numeric user ID
|
||||||
|
* 116 8 Group's numeric user ID
|
||||||
|
* 124 12 File size in bytes
|
||||||
|
* 136 12 Last modification time in numeric Unix time format
|
||||||
|
* 148 8 Checksum for header block
|
||||||
|
* 156 1 Link indicator (file type)
|
||||||
|
* 157 100 Name of linked file
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* File Types
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Value Meaning
|
||||||
|
* '0' Normal file
|
||||||
|
* (ASCII NUL) Normal file (now obsolete)
|
||||||
|
* '1' Hard link
|
||||||
|
* '2' Symbolic link
|
||||||
|
* '3' Character special
|
||||||
|
* '4' Block special
|
||||||
|
* '5' Directory
|
||||||
|
* '6' FIFO
|
||||||
|
* '7' Contigous
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Ustar header
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Offset Size Field
|
||||||
|
* 257 6 UStar indicator "ustar"
|
||||||
|
* 263 2 UStar version "00"
|
||||||
|
* 265 32 Owner user name
|
||||||
|
* 297 32 Owner group name
|
||||||
|
* 329 8 Device major number
|
||||||
|
* 337 8 Device minor number
|
||||||
|
* 345 155 Filename prefix
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TarHeader {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Header
|
||||||
|
*/
|
||||||
|
public static final int NAMELEN = 100;
|
||||||
|
public static final int MODELEN = 8;
|
||||||
|
public static final int UIDLEN = 8;
|
||||||
|
public static final int GIDLEN = 8;
|
||||||
|
public static final int SIZELEN = 12;
|
||||||
|
public static final int MODTIMELEN = 12;
|
||||||
|
public static final int CHKSUMLEN = 8;
|
||||||
|
public static final byte LF_OLDNORM = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File Types
|
||||||
|
*/
|
||||||
|
public static final byte LF_NORMAL = (byte) '0';
|
||||||
|
public static final byte LF_LINK = (byte) '1';
|
||||||
|
public static final byte LF_SYMLINK = (byte) '2';
|
||||||
|
public static final byte LF_CHR = (byte) '3';
|
||||||
|
public static final byte LF_BLK = (byte) '4';
|
||||||
|
public static final byte LF_DIR = (byte) '5';
|
||||||
|
public static final byte LF_FIFO = (byte) '6';
|
||||||
|
public static final byte LF_CONTIG = (byte) '7';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ustar header
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final String USTAR_MAGIC = "ustar"; // POSIX
|
||||||
|
|
||||||
|
public static final int USTAR_MAGICLEN = 8;
|
||||||
|
public static final int USTAR_USER_NAMELEN = 32;
|
||||||
|
public static final int USTAR_GROUP_NAMELEN = 32;
|
||||||
|
public static final int USTAR_DEVLEN = 8;
|
||||||
|
public static final int USTAR_FILENAME_PREFIX = 155;
|
||||||
|
|
||||||
|
// Header values
|
||||||
|
public StringBuffer name;
|
||||||
|
public int mode;
|
||||||
|
public int userId;
|
||||||
|
public int groupId;
|
||||||
|
public long size;
|
||||||
|
public long modTime;
|
||||||
|
public int checkSum;
|
||||||
|
public byte linkFlag;
|
||||||
|
public StringBuffer linkName;
|
||||||
|
public StringBuffer magic; // ustar indicator and version
|
||||||
|
public StringBuffer userName;
|
||||||
|
public StringBuffer groupName;
|
||||||
|
public int devMajor;
|
||||||
|
public int devMinor;
|
||||||
|
public StringBuffer namePrefix;
|
||||||
|
|
||||||
|
public TarHeader() {
|
||||||
|
this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
|
||||||
|
|
||||||
|
this.name = new StringBuffer();
|
||||||
|
this.linkName = new StringBuffer();
|
||||||
|
|
||||||
|
String user = System.getProperty("user.name", "");
|
||||||
|
|
||||||
|
if (user.length() > 31)
|
||||||
|
user = user.substring(0, 31);
|
||||||
|
|
||||||
|
this.userId = 0;
|
||||||
|
this.groupId = 0;
|
||||||
|
this.userName = new StringBuffer(user);
|
||||||
|
this.groupName = new StringBuffer("");
|
||||||
|
this.namePrefix = new StringBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an entry name from a header buffer.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param header
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
* @return The header's entry name.
|
||||||
|
*/
|
||||||
|
public static StringBuffer parseName(byte[] header, int offset, int length) {
|
||||||
|
StringBuffer result = new StringBuffer(length);
|
||||||
|
|
||||||
|
int end = offset + length;
|
||||||
|
for (int i = offset; i < end; ++i) {
|
||||||
|
if (header[i] == 0)
|
||||||
|
break;
|
||||||
|
result.append((char) header[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the number of bytes in an entry name.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param header
|
||||||
|
* The header buffer from which to parse.
|
||||||
|
* @param offset
|
||||||
|
* The offset into the buffer from which to parse.
|
||||||
|
* @param length
|
||||||
|
* The number of header bytes to parse.
|
||||||
|
* @return The number of bytes in a header's entry name.
|
||||||
|
*/
|
||||||
|
public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < length && i < name.length(); ++i) {
|
||||||
|
buf[offset + i] = (byte) name.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < length; ++i) {
|
||||||
|
buf[offset + i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new header for a file/directory entry.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* File name
|
||||||
|
* @param size
|
||||||
|
* File size in bytes
|
||||||
|
* @param modTime
|
||||||
|
* Last modification time in numeric Unix time format
|
||||||
|
* @param dir
|
||||||
|
* Is directory
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
|
||||||
|
String name = entryName;
|
||||||
|
name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
|
||||||
|
|
||||||
|
TarHeader header = new TarHeader();
|
||||||
|
header.linkName = new StringBuffer("");
|
||||||
|
|
||||||
|
if (name.length() > 100) {
|
||||||
|
header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
|
||||||
|
header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
|
||||||
|
} else {
|
||||||
|
header.name = new StringBuffer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir) {
|
||||||
|
header.mode = 040755;
|
||||||
|
header.linkFlag = TarHeader.LF_DIR;
|
||||||
|
if (header.name.charAt(header.name.length() - 1) != '/') {
|
||||||
|
header.name.append("/");
|
||||||
|
}
|
||||||
|
header.size = 0;
|
||||||
|
} else {
|
||||||
|
header.mode = 0100644;
|
||||||
|
header.linkFlag = TarHeader.LF_NORMAL;
|
||||||
|
header.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.modTime = modTime;
|
||||||
|
header.checkSum = 0;
|
||||||
|
header.devMajor = 0;
|
||||||
|
header.devMinor = 0;
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
private static final int SKIP_BUFFER_SIZE = 2048;
|
||||||
|
private TarEntry currentEntry;
|
||||||
|
private long currentFileSize;
|
||||||
|
private long bytesRead;
|
||||||
|
private boolean defaultSkip = false;
|
||||||
|
|
||||||
|
public TarInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
currentFileSize = 0;
|
||||||
|
bytesRead = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not supported
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int readlimit) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not supported
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
throw new IOException("mark/reset not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte
|
||||||
|
*
|
||||||
|
* @see java.io.FilterInputStream#read()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
byte[] buf = new byte[1];
|
||||||
|
|
||||||
|
int res = this.read(buf, 0, 1);
|
||||||
|
|
||||||
|
if (res != -1) {
|
||||||
|
return 0xFF & buf[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bytes being read exceed the entry size and adjusts the byte
|
||||||
|
* array length. Updates the byte counters
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see java.io.FilterInputStream#read(byte[], int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentFileSize == currentEntry.getSize()) {
|
||||||
|
return -1;
|
||||||
|
} else if ((currentEntry.getSize() - currentFileSize) < len) {
|
||||||
|
len = (int) (currentEntry.getSize() - currentFileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int br = super.read(b, off, len);
|
||||||
|
|
||||||
|
if (br != -1) {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
currentFileSize += br;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead += br;
|
||||||
|
}
|
||||||
|
|
||||||
|
return br;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next entry in the tar file
|
||||||
|
*
|
||||||
|
* @return TarEntry
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public TarEntry getNextEntry() throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
|
||||||
|
byte[] header = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
byte[] theader = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
int tr = 0;
|
||||||
|
|
||||||
|
// Read full header
|
||||||
|
while (tr < TarConstants.HEADER_BLOCK) {
|
||||||
|
int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(theader, 0, header, tr, res);
|
||||||
|
tr += res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if record is null
|
||||||
|
boolean eof = true;
|
||||||
|
for (byte b : header) {
|
||||||
|
if (b != 0) {
|
||||||
|
eof = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eof) {
|
||||||
|
currentEntry = new TarEntry(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current offset (in bytes) from the beginning of the stream.
|
||||||
|
* This can be used to find out at which point in a tar file an entry's content begins, for instance.
|
||||||
|
*/
|
||||||
|
public long getCurrentOffset() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current tar entry
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void closeCurrentEntry() throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentEntry.getSize() > currentFileSize) {
|
||||||
|
// Not fully read, skip rest of the bytes
|
||||||
|
long bs = 0;
|
||||||
|
while (bs < currentEntry.getSize() - currentFileSize) {
|
||||||
|
long res = skip(currentEntry.getSize() - currentFileSize - bs);
|
||||||
|
|
||||||
|
if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
|
||||||
|
// I suspect file corruption
|
||||||
|
throw new IOException("Possible tar file corruption");
|
||||||
|
}
|
||||||
|
|
||||||
|
bs += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEntry = null;
|
||||||
|
currentFileSize = 0L;
|
||||||
|
skipPad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the pad at the end of each tar entry file content
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void skipPad() throws IOException {
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
long bs = 0;
|
||||||
|
while (bs < TarConstants.DATA_BLOCK - extra) {
|
||||||
|
long res = skip(TarConstants.DATA_BLOCK - extra - bs);
|
||||||
|
bs += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips 'n' bytes on the InputStream<br>
|
||||||
|
* Overrides default implementation of skip
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
if (defaultSkip) {
|
||||||
|
// use skip method of parent stream
|
||||||
|
// may not work if skip not implemented by parent
|
||||||
|
long bs = super.skip(n);
|
||||||
|
bytesRead += bs;
|
||||||
|
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long left = n;
|
||||||
|
byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
|
||||||
|
if (res < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left -= res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n - left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultSkip() {
|
||||||
|
return defaultSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultSkip(boolean defaultSkip) {
|
||||||
|
this.defaultSkip = defaultSkip;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran Zafar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarOutputStream extends OutputStream {
|
||||||
|
private final OutputStream out;
|
||||||
|
private long bytesWritten;
|
||||||
|
private long currentFileSize;
|
||||||
|
private TarEntry currentEntry;
|
||||||
|
|
||||||
|
public TarOutputStream(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
bytesWritten = 0;
|
||||||
|
currentFileSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TarOutputStream(final File fout) throws FileNotFoundException {
|
||||||
|
this.out = new BufferedOutputStream(new FileOutputStream(fout));
|
||||||
|
bytesWritten = 0;
|
||||||
|
currentFileSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file for writing.
|
||||||
|
*/
|
||||||
|
public TarOutputStream(final File fout, final boolean append) throws IOException {
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
RandomAccessFile raf = new RandomAccessFile(fout, "rw");
|
||||||
|
final long fileSize = fout.length();
|
||||||
|
if (append && fileSize > TarConstants.EOF_BLOCK) {
|
||||||
|
raf.seek(fileSize - TarConstants.EOF_BLOCK);
|
||||||
|
}
|
||||||
|
out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the EOF record and closes the stream
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#close()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
write( new byte[TarConstants.EOF_BLOCK] );
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Writes a byte to the stream and updates byte counters
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#write(int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
out.write( b );
|
||||||
|
bytesWritten += 1;
|
||||||
|
|
||||||
|
if (currentEntry != null) {
|
||||||
|
currentFileSize += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bytes being written exceed the current entry size.
|
||||||
|
*
|
||||||
|
* @see java.io.FilterOutputStream#write(byte[], int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (currentEntry != null && !currentEntry.isDirectory()) {
|
||||||
|
if (currentEntry.getSize() < currentFileSize + len) {
|
||||||
|
throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
|
||||||
|
+ currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
|
||||||
|
+ "] being written." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write( b, off, len );
|
||||||
|
|
||||||
|
bytesWritten += len;
|
||||||
|
|
||||||
|
if (currentEntry != null) {
|
||||||
|
currentFileSize += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the next tar entry header on the stream
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void putNextEntry(TarEntry entry) throws IOException {
|
||||||
|
closeCurrentEntry();
|
||||||
|
|
||||||
|
byte[] header = new byte[TarConstants.HEADER_BLOCK];
|
||||||
|
entry.writeEntryHeader( header );
|
||||||
|
|
||||||
|
write( header );
|
||||||
|
|
||||||
|
currentEntry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current tar entry
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void closeCurrentEntry() throws IOException {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
if (currentEntry.getSize() > currentFileSize) {
|
||||||
|
throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
|
||||||
|
+ currentEntry.getSize() + "] has not been fully written." );
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEntry = null;
|
||||||
|
currentFileSize = 0;
|
||||||
|
|
||||||
|
pad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pads the last content block
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void pad() throws IOException {
|
||||||
|
if (bytesWritten > 0) {
|
||||||
|
int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
write( new byte[TarConstants.DATA_BLOCK - extra] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2012 Kamran Zafar
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kamranzafar.jtar;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kamran
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TarUtils {
|
||||||
|
/**
|
||||||
|
* Determines the tar file size of the given folder/file path
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static long calculateTarSize(File path) {
|
||||||
|
return tarSize(path) + TarConstants.EOF_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long tarSize(File dir) {
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
if (dir.isFile()) {
|
||||||
|
return entrySize(dir.length());
|
||||||
|
} else {
|
||||||
|
File[] subFiles = dir.listFiles();
|
||||||
|
|
||||||
|
if (subFiles != null && subFiles.length > 0) {
|
||||||
|
for (File file : subFiles) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
size += entrySize(file.length());
|
||||||
|
} else {
|
||||||
|
size += tarSize(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Empty folder header
|
||||||
|
return TarConstants.HEADER_BLOCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long entrySize(long fileSize) {
|
||||||
|
long size = 0;
|
||||||
|
size += TarConstants.HEADER_BLOCK; // Header
|
||||||
|
size += fileSize; // File size
|
||||||
|
|
||||||
|
long extra = size % TarConstants.DATA_BLOCK;
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
size += (TarConstants.DATA_BLOCK - extra); // pad
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String trim(String s, char c) {
|
||||||
|
StringBuffer tmp = new StringBuffer(s);
|
||||||
|
for (int i = 0; i < tmp.length(); i++) {
|
||||||
|
if (tmp.charAt(i) != c) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tmp.deleteCharAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = tmp.length() - 1; i >= 0; i--) {
|
||||||
|
if (tmp.charAt(i) != c) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tmp.deleteCharAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class GenericBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
GenericBroadcastReceiverCallback listener;
|
||||||
|
|
||||||
|
public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {
|
||||||
|
super();
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
this.listener.onReceive(context, intent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public interface GenericBroadcastReceiverCallback {
|
||||||
|
void onReceive(Context context, Intent intent);
|
||||||
|
};
|
|
@ -0,0 +1,479 @@
|
||||||
|
|
||||||
|
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("PYTHONHOME", app_root_dir);
|
||||||
|
SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
|
||||||
|
SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "Access to our meta-data...");
|
||||||
|
mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
|
||||||
|
mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
|
||||||
|
|
||||||
|
PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
|
||||||
|
if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
|
||||||
|
mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
|
||||||
|
mActivity.mWakeLock.acquire();
|
||||||
|
}
|
||||||
|
if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) {
|
||||||
|
Log.v(TAG, "Surface will be transparent.");
|
||||||
|
getSurface().setZOrderOnTop(true);
|
||||||
|
getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Surface will NOT be transparent");
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Void... values) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unpackData(final String resource, File target) {
|
||||||
|
|
||||||
|
Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
|
||||||
|
|
||||||
|
// The version of data in memory and on disk.
|
||||||
|
String data_version = resourceManager.getString(resource + "_version");
|
||||||
|
String disk_version = null;
|
||||||
|
|
||||||
|
Log.v(TAG, "Data version is " + data_version);
|
||||||
|
|
||||||
|
// If no version, no unpacking is necessary.
|
||||||
|
if (data_version == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the current disk version, if any.
|
||||||
|
String filesDir = target.getAbsolutePath();
|
||||||
|
String disk_version_fn = filesDir + "/" + resource + ".version";
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte buf[] = new byte[64];
|
||||||
|
InputStream is = new FileInputStream(disk_version_fn);
|
||||||
|
int len = is.read(buf);
|
||||||
|
disk_version = new String(buf, 0, len);
|
||||||
|
is.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
disk_version = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the disk data is out of date, extract it and write the
|
||||||
|
// version file.
|
||||||
|
// if (! data_version.equals(disk_version)) {
|
||||||
|
if (! data_version.equals(disk_version)) {
|
||||||
|
Log.v(TAG, "Extracting " + resource + " assets.");
|
||||||
|
|
||||||
|
recursiveDelete(target);
|
||||||
|
target.mkdirs();
|
||||||
|
|
||||||
|
AssetExtract ae = new AssetExtract(this);
|
||||||
|
if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
|
||||||
|
toastError("Could not extract " + resource + " data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write .nomedia.
|
||||||
|
new File(target, ".nomedia").createNewFile();
|
||||||
|
|
||||||
|
// Write version file.
|
||||||
|
FileOutputStream os = new FileOutputStream(disk_version_fn);
|
||||||
|
os.write(data_version.getBytes());
|
||||||
|
os.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w("python", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewGroup getLayout() {
|
||||||
|
return mLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SurfaceView getSurface() {
|
||||||
|
return mSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Listener interface for onNewIntent
|
||||||
|
//
|
||||||
|
|
||||||
|
public interface NewIntentListener {
|
||||||
|
void onNewIntent(Intent intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<NewIntentListener> newIntentListeners = null;
|
||||||
|
|
||||||
|
public void registerNewIntentListener(NewIntentListener listener) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
|
||||||
|
this.newIntentListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterNewIntentListener(NewIntentListener listener) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
return;
|
||||||
|
this.newIntentListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
if ( this.newIntentListeners == null )
|
||||||
|
return;
|
||||||
|
this.onResume();
|
||||||
|
synchronized ( this.newIntentListeners ) {
|
||||||
|
Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();
|
||||||
|
while ( iterator.hasNext() ) {
|
||||||
|
(iterator.next()).onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Listener interface for onActivityResult
|
||||||
|
//
|
||||||
|
|
||||||
|
public interface ActivityResultListener {
|
||||||
|
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ActivityResultListener> activityResultListeners = null;
|
||||||
|
|
||||||
|
public void registerActivityResultListener(ActivityResultListener listener) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
|
||||||
|
this.activityResultListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterActivityResultListener(ActivityResultListener listener) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
return;
|
||||||
|
this.activityResultListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
if ( this.activityResultListeners == null )
|
||||||
|
return;
|
||||||
|
this.onResume();
|
||||||
|
synchronized ( this.activityResultListeners ) {
|
||||||
|
Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();
|
||||||
|
while ( iterator.hasNext() )
|
||||||
|
(iterator.next()).onActivityResult(requestCode, resultCode, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start_service(String serviceTitle, String serviceDescription,
|
||||||
|
String pythonServiceArgument) {
|
||||||
|
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
|
||||||
|
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
|
||||||
|
String filesDirectory = argument;
|
||||||
|
String app_root_dir = PythonActivity.mActivity.getAppRoot();
|
||||||
|
serviceIntent.putExtra("androidPrivate", argument);
|
||||||
|
serviceIntent.putExtra("androidArgument", app_root_dir);
|
||||||
|
serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
|
||||||
|
serviceIntent.putExtra("pythonHome", app_root_dir);
|
||||||
|
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
|
||||||
|
serviceIntent.putExtra("serviceTitle", serviceTitle);
|
||||||
|
serviceIntent.putExtra("serviceDescription", serviceDescription);
|
||||||
|
serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||||
|
PythonActivity.mActivity.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop_service() {
|
||||||
|
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
|
||||||
|
PythonActivity.mActivity.stopService(serviceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loading screen implementation
|
||||||
|
* keepActive() is a method plugged in pollInputDevices in SDLActivity.
|
||||||
|
* Once it's called twice, the loading screen will be removed.
|
||||||
|
* The first call happen as soon as the window is created, but no image has been
|
||||||
|
* displayed first. My tests showed that we can wait one more. This might delay
|
||||||
|
* the real available of few hundred milliseconds.
|
||||||
|
* The real deal is to know if a rendering has already happen. The previous
|
||||||
|
* python-for-android and kivy was having something for that, but this new version
|
||||||
|
* is not compatible, and would require a new kivy version.
|
||||||
|
* In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called.
|
||||||
|
*/
|
||||||
|
public static ImageView mImageView = null;
|
||||||
|
int mLoadingCount = 2;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keepActive() {
|
||||||
|
if (this.mLoadingCount > 0) {
|
||||||
|
this.mLoadingCount -= 1;
|
||||||
|
if (this.mLoadingCount == 0) {
|
||||||
|
this.removeLoadingScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeLoadingScreen() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (PythonActivity.mImageView != null &&
|
||||||
|
PythonActivity.mImageView.getParent() != null) {
|
||||||
|
((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
|
||||||
|
PythonActivity.mImageView);
|
||||||
|
PythonActivity.mImageView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void showLoadingScreen() {
|
||||||
|
// load the bitmap
|
||||||
|
// 1. if the image is valid and we don't have layout yet, assign this bitmap
|
||||||
|
// as main view.
|
||||||
|
// 2. if we have a layout, just set it in the layout.
|
||||||
|
// 3. If we have an mImageView already, then do nothing because it will have
|
||||||
|
// already been made the content view or added to the layout.
|
||||||
|
|
||||||
|
if (mImageView == null) {
|
||||||
|
int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
|
||||||
|
InputStream is = this.getResources().openRawResource(presplashId);
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
try {
|
||||||
|
bitmap = BitmapFactory.decodeStream(is);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mImageView = new ImageView(this);
|
||||||
|
mImageView.setImageBitmap(bitmap);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the presplash loading screen background color
|
||||||
|
* https://developer.android.com/reference/android/graphics/Color.html
|
||||||
|
* Parse the color string, and return the corresponding color-int.
|
||||||
|
* If the string cannot be parsed, throws an IllegalArgumentException exception.
|
||||||
|
* Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
|
||||||
|
* 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
|
||||||
|
* 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
|
||||||
|
* 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
|
||||||
|
*/
|
||||||
|
String backgroundColor = resourceManager.getString("presplash_color");
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
try {
|
||||||
|
mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
|
||||||
|
} catch (IllegalArgumentException e) {}
|
||||||
|
}
|
||||||
|
mImageView.setLayoutParams(new ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.FILL_PARENT,
|
||||||
|
ViewGroup.LayoutParams.FILL_PARENT));
|
||||||
|
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLayout == null) {
|
||||||
|
setContentView(mImageView);
|
||||||
|
} else if (PythonActivity.mImageView.getParent() == null){
|
||||||
|
mLayout.addView(mImageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
// fooabc
|
||||||
|
if ( this.mWakeLock != null && mWakeLock.isHeld()){
|
||||||
|
this.mWakeLock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "onPause()");
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
if ( this.mWakeLock != null){
|
||||||
|
this.mWakeLock.acquire();
|
||||||
|
}
|
||||||
|
Log.v(TAG, "onResume()");
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Process;
|
||||||
|
import 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");
|
||||||
|
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Notification notification = new Notification(context.getApplicationInfo().icon,
|
||||||
|
serviceTitle, System.currentTimeMillis());
|
||||||
|
Intent contextIntent = new Intent(context, PythonActivity.class);
|
||||||
|
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
notification.setLatestEventInfo(context, serviceTitle, serviceDescription, pIntent);
|
||||||
|
startForeground(1, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
pythonThread = null;
|
||||||
|
if (autoRestartService && startIntent != null) {
|
||||||
|
Log.v("python service", "service restart requested");
|
||||||
|
startService(startIntent);
|
||||||
|
}
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(){
|
||||||
|
String app_root = getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
File app_root_file = new File(app_root);
|
||||||
|
PythonUtil.loadLibraries(app_root_file);
|
||||||
|
this.mService = this;
|
||||||
|
nativeStart(
|
||||||
|
androidPrivate, androidArgument,
|
||||||
|
serviceEntrypoint, pythonName,
|
||||||
|
pythonHome, pythonPath,
|
||||||
|
pythonServiceArgument);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native part
|
||||||
|
public static native void nativeStart(
|
||||||
|
String androidPrivate, String androidArgument,
|
||||||
|
String serviceEntrypoint, String pythonName,
|
||||||
|
String pythonHome, String pythonPath,
|
||||||
|
String pythonServiceArgument);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
public class PythonUtil {
|
||||||
|
private static final String TAG = "PythonUtil";
|
||||||
|
|
||||||
|
protected static String[] getLibraries() {
|
||||||
|
return new String[] {
|
||||||
|
"SDL2",
|
||||||
|
"SDL2_image",
|
||||||
|
"SDL2_mixer",
|
||||||
|
"SDL2_ttf",
|
||||||
|
"python2.7",
|
||||||
|
"python3.5m",
|
||||||
|
"python3.6m",
|
||||||
|
"main"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadLibraries(File filesDir) {
|
||||||
|
|
||||||
|
String filesDirPath = filesDir.getAbsolutePath();
|
||||||
|
boolean foundPython = false;
|
||||||
|
|
||||||
|
for (String lib : getLibraries()) {
|
||||||
|
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
|
||||||
|
if (lib.startsWith("python3.6") && !foundPython) {
|
||||||
|
throw new java.lang.RuntimeException("Could not load any libpythonXXX.so");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so");
|
||||||
|
System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so");
|
||||||
|
} catch(UnsatisfiedLinkError e) {
|
||||||
|
Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// System.loadLibrary("ctypes");
|
||||||
|
System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so");
|
||||||
|
} catch(UnsatisfiedLinkError e) {
|
||||||
|
Log.v(TAG, "Unsatisfied linker when loading ctypes");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "Loaded everything!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.kivy.android.concurrency;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ryan on 3/28/14.
|
||||||
|
*/
|
||||||
|
public class PythonEvent {
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
private final Condition cond = lock.newCondition();
|
||||||
|
private boolean flag = false;
|
||||||
|
|
||||||
|
public void set() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
flag = true;
|
||||||
|
cond.signalAll();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void wait_() throws InterruptedException {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
while (!flag) {
|
||||||
|
cond.await();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
flag = false;
|
||||||
|
cond.signalAll();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.kivy.android.concurrency;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ryan on 3/28/14.
|
||||||
|
*/
|
||||||
|
public class PythonLock {
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
public void acquire() {
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package org.kivy.android.launcher;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a project we've scanned for.
|
||||||
|
*/
|
||||||
|
public class Project {
|
||||||
|
|
||||||
|
public String dir = null;
|
||||||
|
String title = null;
|
||||||
|
String author = null;
|
||||||
|
Bitmap icon = null;
|
||||||
|
public boolean landscape = false;
|
||||||
|
|
||||||
|
static String decode(String s) {
|
||||||
|
try {
|
||||||
|
return new String(s.getBytes("ISO-8859-1"), "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans directory for a android.txt file. If it finds one,
|
||||||
|
* and it looks valid enough, then it creates a new Project,
|
||||||
|
* and returns that. Otherwise, returns null.
|
||||||
|
*/
|
||||||
|
public static Project scanDirectory(File dir) {
|
||||||
|
|
||||||
|
// We might have a link file.
|
||||||
|
if (dir.getAbsolutePath().endsWith(".link")) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Scan the android.txt file.
|
||||||
|
File propfile = new File(dir, "android.txt");
|
||||||
|
FileInputStream in = new FileInputStream(propfile);
|
||||||
|
Properties p = new Properties();
|
||||||
|
p.load(in);
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
String directory = p.getProperty("directory", null);
|
||||||
|
|
||||||
|
if (directory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = new File(directory);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.i("Project", "Couldn't open link file " + dir, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we're dealing with a directory.
|
||||||
|
if (! dir.isDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Scan the android.txt file.
|
||||||
|
File propfile = new File(dir, "android.txt");
|
||||||
|
FileInputStream in = new FileInputStream(propfile);
|
||||||
|
Properties p = new Properties();
|
||||||
|
p.load(in);
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
// Get the various properties.
|
||||||
|
String title = decode(p.getProperty("title", "Untitled"));
|
||||||
|
String author = decode(p.getProperty("author", ""));
|
||||||
|
boolean landscape = p.getProperty("orientation", "portrait").equals("landscape");
|
||||||
|
|
||||||
|
// Create the project object.
|
||||||
|
Project rv = new Project();
|
||||||
|
rv.title = title;
|
||||||
|
rv.author = author;
|
||||||
|
rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath());
|
||||||
|
rv.landscape = landscape;
|
||||||
|
rv.dir = dir.getAbsolutePath();
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.i("Project", "Couldn't open android.txt", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.kivy.android.launcher;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.renpy.android.ResourceManager;
|
||||||
|
|
||||||
|
public class ProjectAdapter extends ArrayAdapter<Project> {
|
||||||
|
|
||||||
|
private Activity mContext;
|
||||||
|
private ResourceManager resourceManager;
|
||||||
|
|
||||||
|
public ProjectAdapter(Activity context) {
|
||||||
|
super(context, 0);
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
resourceManager = new ResourceManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
Project p = getItem(position);
|
||||||
|
|
||||||
|
View v = resourceManager.inflateView("chooser_item");
|
||||||
|
TextView title = (TextView) resourceManager.getViewById(v, "title");
|
||||||
|
TextView author = (TextView) resourceManager.getViewById(v, "author");
|
||||||
|
ImageView icon = (ImageView) resourceManager.getViewById(v, "icon");
|
||||||
|
|
||||||
|
title.setText(p.title);
|
||||||
|
author.setText(p.author);
|
||||||
|
icon.setImageBitmap(p.icon);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.kivy.android.launcher;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.renpy.android.ResourceManager;
|
||||||
|
|
||||||
|
public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
|
ResourceManager resourceManager;
|
||||||
|
|
||||||
|
String urlScheme;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart()
|
||||||
|
{
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
resourceManager = new ResourceManager(this);
|
||||||
|
|
||||||
|
urlScheme = resourceManager.getString("urlScheme");
|
||||||
|
|
||||||
|
// Set the window title.
|
||||||
|
setTitle(resourceManager.getString("appName"));
|
||||||
|
|
||||||
|
// Scan the sdcard for files, and sort them.
|
||||||
|
File dir = new File(Environment.getExternalStorageDirectory(), urlScheme);
|
||||||
|
|
||||||
|
File entries[] = dir.listFiles();
|
||||||
|
|
||||||
|
if (entries == null) {
|
||||||
|
entries = new File[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.sort(entries);
|
||||||
|
|
||||||
|
// Create a ProjectAdapter and fill it with projects.
|
||||||
|
ProjectAdapter projectAdapter = new ProjectAdapter(this);
|
||||||
|
|
||||||
|
// Populate it with the properties files.
|
||||||
|
for (File d : entries) {
|
||||||
|
Project p = Project.scanDirectory(d);
|
||||||
|
if (p != null) {
|
||||||
|
projectAdapter.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectAdapter.getCount() != 0) {
|
||||||
|
|
||||||
|
View v = resourceManager.inflateView("project_chooser");
|
||||||
|
ListView l = (ListView) resourceManager.getViewById(v, "projectList");
|
||||||
|
|
||||||
|
l.setAdapter(projectAdapter);
|
||||||
|
l.setOnItemClickListener(this);
|
||||||
|
|
||||||
|
setContentView(v);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
View v = resourceManager.inflateView("project_empty");
|
||||||
|
TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText");
|
||||||
|
|
||||||
|
emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit.");
|
||||||
|
|
||||||
|
setContentView(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onItemClick(AdapterView parent, View view, int position, long id) {
|
||||||
|
Project p = (Project) parent.getItemAtPosition(position);
|
||||||
|
|
||||||
|
Intent intent = new Intent(
|
||||||
|
"org.kivy.LAUNCH",
|
||||||
|
Uri.fromParts(urlScheme, p.dir, ""));
|
||||||
|
|
||||||
|
intent.setClassName(getPackageName(), "org.kivy.android.PythonActivity");
|
||||||
|
this.startActivity(intent);
|
||||||
|
this.finish();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
// 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.content.Context;
|
||||||
|
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 Context mActivity = null;
|
||||||
|
|
||||||
|
public AssetExtract(Context ctx) {
|
||||||
|
mActivity = ctx;
|
||||||
|
mAssetManager = ctx.getAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean extractTar(String asset, String target) {
|
||||||
|
|
||||||
|
byte buf[] = new byte[1024 * 1024];
|
||||||
|
|
||||||
|
InputStream assetStream = null;
|
||||||
|
TarInputStream tis = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
|
||||||
|
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("python", "opening up extract tar", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
TarEntry entry = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
entry = tis.getNextEntry();
|
||||||
|
} catch ( java.io.IOException e ) {
|
||||||
|
Log.e("python", "extracting tar", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( entry == null ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("python", "extracting " + entry.getName());
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
new File(target +"/" + entry.getName()).mkdirs();
|
||||||
|
} catch ( SecurityException e ) { };
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream out = null;
|
||||||
|
String path = target + "/" + entry.getName();
|
||||||
|
|
||||||
|
try {
|
||||||
|
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
|
||||||
|
} catch ( FileNotFoundException e ) {
|
||||||
|
} catch ( SecurityException e ) { };
|
||||||
|
|
||||||
|
if ( out == null ) {
|
||||||
|
Log.e("python", "could not open " + path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
int len = tis.read(buf);
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
} catch ( java.io.IOException e ) {
|
||||||
|
Log.e("python", "extracting zip", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
tis.close();
|
||||||
|
assetStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import android.net.wifi.ScanResult;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
import org.kivy.android.PythonActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods that are expected to be called via JNI, to access the
|
||||||
|
* device's non-screen hardware. (For example, the vibration and
|
||||||
|
* accelerometer.)
|
||||||
|
*/
|
||||||
|
public class Hardware {
|
||||||
|
|
||||||
|
// The context.
|
||||||
|
static Context context;
|
||||||
|
static View view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vibrate for s seconds.
|
||||||
|
*/
|
||||||
|
public static void vibrate(double s) {
|
||||||
|
Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
if (v != null) {
|
||||||
|
v.vibrate((int) (1000 * s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an Overview of all Hardware Sensors of an Android Device
|
||||||
|
*/
|
||||||
|
public static String getHardwareSensors() {
|
||||||
|
SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);
|
||||||
|
|
||||||
|
if (allSensors != null) {
|
||||||
|
String resultString = "";
|
||||||
|
for (Sensor s : allSensors) {
|
||||||
|
resultString += String.format("Name=" + s.getName());
|
||||||
|
resultString += String.format(",Vendor=" + s.getVendor());
|
||||||
|
resultString += String.format(",Version=" + s.getVersion());
|
||||||
|
resultString += String.format(",MaximumRange=" + s.getMaximumRange());
|
||||||
|
// XXX MinDelay is not in the 2.2
|
||||||
|
//resultString += String.format(",MinDelay=" + s.getMinDelay());
|
||||||
|
resultString += String.format(",Power=" + s.getPower());
|
||||||
|
resultString += String.format(",Type=" + s.getType() + "\n");
|
||||||
|
}
|
||||||
|
return resultString;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors
|
||||||
|
*/
|
||||||
|
public static class generic3AxisSensor implements SensorEventListener {
|
||||||
|
private final SensorManager sSensorManager;
|
||||||
|
private final Sensor sSensor;
|
||||||
|
private final int sSensorType;
|
||||||
|
SensorEvent sSensorEvent;
|
||||||
|
|
||||||
|
public generic3AxisSensor(int sensorType) {
|
||||||
|
sSensorType = sensorType;
|
||||||
|
sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
sSensor = sSensorManager.getDefaultSensor(sSensorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
sSensorEvent = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the Sensor by registering/unregistering
|
||||||
|
*/
|
||||||
|
public void changeStatus(boolean enable) {
|
||||||
|
if (enable) {
|
||||||
|
sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||||
|
} else {
|
||||||
|
sSensorManager.unregisterListener(this, sSensor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the Sensor
|
||||||
|
*/
|
||||||
|
public float[] readSensor() {
|
||||||
|
if (sSensorEvent != null) {
|
||||||
|
return sSensorEvent.values;
|
||||||
|
} else {
|
||||||
|
float rv[] = { 0f, 0f, 0f };
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generic3AxisSensor accelerometerSensor = null;
|
||||||
|
public static generic3AxisSensor orientationSensor = null;
|
||||||
|
public static generic3AxisSensor magneticFieldSensor = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* functions for backward compatibility reasons
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void accelerometerEnable(boolean enable) {
|
||||||
|
if ( accelerometerSensor == null )
|
||||||
|
accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);
|
||||||
|
accelerometerSensor.changeStatus(enable);
|
||||||
|
}
|
||||||
|
public static float[] accelerometerReading() {
|
||||||
|
float rv[] = { 0f, 0f, 0f };
|
||||||
|
if ( accelerometerSensor == null )
|
||||||
|
return rv;
|
||||||
|
return (float[]) accelerometerSensor.readSensor();
|
||||||
|
}
|
||||||
|
public static void orientationSensorEnable(boolean enable) {
|
||||||
|
if ( orientationSensor == null )
|
||||||
|
orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
|
||||||
|
orientationSensor.changeStatus(enable);
|
||||||
|
}
|
||||||
|
public static float[] orientationSensorReading() {
|
||||||
|
float rv[] = { 0f, 0f, 0f };
|
||||||
|
if ( orientationSensor == null )
|
||||||
|
return rv;
|
||||||
|
return (float[]) orientationSensor.readSensor();
|
||||||
|
}
|
||||||
|
public static void magneticFieldSensorEnable(boolean enable) {
|
||||||
|
if ( magneticFieldSensor == null )
|
||||||
|
magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);
|
||||||
|
magneticFieldSensor.changeStatus(enable);
|
||||||
|
}
|
||||||
|
public static float[] magneticFieldSensorReading() {
|
||||||
|
float rv[] = { 0f, 0f, 0f };
|
||||||
|
if ( magneticFieldSensor == null )
|
||||||
|
return rv;
|
||||||
|
return (float[]) magneticFieldSensor.readSensor();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public DisplayMetrics metrics = new DisplayMetrics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get display DPI.
|
||||||
|
*/
|
||||||
|
public static int getDPI() {
|
||||||
|
// AND: Shouldn't have to get the metrics like this every time...
|
||||||
|
PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||||
|
return metrics.densityDpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Show the soft keyboard.
|
||||||
|
// */
|
||||||
|
// public static void showKeyboard(int input_type) {
|
||||||
|
// //Log.i("python", "hardware.Java show_keyword " input_type);
|
||||||
|
|
||||||
|
// InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
|
||||||
|
// SDLSurfaceView vw = (SDLSurfaceView) view;
|
||||||
|
|
||||||
|
// int inputType = input_type;
|
||||||
|
|
||||||
|
// if (vw.inputType != inputType){
|
||||||
|
// vw.inputType = inputType;
|
||||||
|
// imm.restartInput(view);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the soft keyboard.
|
||||||
|
*/
|
||||||
|
public static void hideKeyboard() {
|
||||||
|
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan WiFi networks
|
||||||
|
*/
|
||||||
|
static List<ScanResult> latestResult;
|
||||||
|
|
||||||
|
public static void enableWifiScanner()
|
||||||
|
{
|
||||||
|
IntentFilter i = new IntentFilter();
|
||||||
|
i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
|
||||||
|
|
||||||
|
context.registerReceiver(new BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context c, Intent i) {
|
||||||
|
// Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs
|
||||||
|
WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
latestResult = w.getScanResults(); // Returns a <list> of scanResults
|
||||||
|
}
|
||||||
|
|
||||||
|
}, i);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scanWifi() {
|
||||||
|
|
||||||
|
// Now you can call this and it should execute the broadcastReceiver's
|
||||||
|
// onReceive()
|
||||||
|
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
boolean a = wm.startScan();
|
||||||
|
|
||||||
|
if (latestResult != null){
|
||||||
|
|
||||||
|
String latestResultString = "";
|
||||||
|
for (ScanResult result : latestResult)
|
||||||
|
{
|
||||||
|
latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestResultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* network state
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static boolean network_state = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check network state directly
|
||||||
|
*
|
||||||
|
* (only one connection can be active at a given moment, detects all network type)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static boolean checkNetwork()
|
||||||
|
{
|
||||||
|
boolean state = false;
|
||||||
|
final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
|
||||||
|
if (activeNetwork != null && activeNetwork.isConnected()) {
|
||||||
|
state = true;
|
||||||
|
} else {
|
||||||
|
state = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To recieve network state changes
|
||||||
|
*/
|
||||||
|
public static void registerNetworkCheck()
|
||||||
|
{
|
||||||
|
IntentFilter i = new IntentFilter();
|
||||||
|
i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||||
|
context.registerReceiver(new BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context c, Intent i) {
|
||||||
|
network_state = checkNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
class PythonActivity extends org.kivy.android.PythonActivity {
|
||||||
|
static {
|
||||||
|
Log.w("PythonActivity", "Accessing org.renpy.android.PythonActivity "
|
||||||
|
+ "is deprecated and will be removed in a "
|
||||||
|
+ "future version. Please switch to "
|
||||||
|
+ "org.kivy.android.PythonActivity.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
class PythonService extends org.kivy.android.PythonService {
|
||||||
|
static {
|
||||||
|
Log.w("PythonService", "Accessing org.renpy.android.PythonService "
|
||||||
|
+ "is deprecated and will be removed in a "
|
||||||
|
+ "future version. Please switch to "
|
||||||
|
+ "org.kivy.android.PythonService.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* This class takes care of managing resources for us. In our code, we
|
||||||
|
* can't use R, since the name of the package containing R will
|
||||||
|
* change. (This same code is used in both org.renpy.android and
|
||||||
|
* org.renpy.pygame.) So this is the next best thing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class ResourceManager {
|
||||||
|
|
||||||
|
private Context act;
|
||||||
|
private Resources res;
|
||||||
|
|
||||||
|
public ResourceManager(Context ctx) {
|
||||||
|
act = ctx;
|
||||||
|
res = ctx.getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIdentifier(String name, String kind) {
|
||||||
|
Log.v("SDL", "getting identifier");
|
||||||
|
Log.v("SDL", "kind is " + kind + " and name " + name);
|
||||||
|
Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName()));
|
||||||
|
return res.getIdentifier(name, kind, act.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String name) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.v("SDL", "asked to get string " + name);
|
||||||
|
return res.getString(getIdentifier(name, "string"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v("SDL", "got exception looking for string!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public View inflateView(String name) {
|
||||||
|
int id = getIdentifier(name, "layout");
|
||||||
|
return null;
|
||||||
|
//return act.getLayoutInflater().inflate(id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getViewById(View v, String name) {
|
||||||
|
int id = getIdentifier(name, "id");
|
||||||
|
return v.findViewById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Replace org.libsdl.app with the identifier of your game below, e.g.
|
||||||
|
com.gamemaker.game
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="{{ args.package }}"
|
||||||
|
android:versionCode="{{ args.numeric_version }}"
|
||||||
|
android:versionName="{{ args.version }}"
|
||||||
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<supports-screens
|
||||||
|
android:smallScreens="true"
|
||||||
|
android:normalScreens="true"
|
||||||
|
android:largeScreens="true"
|
||||||
|
android:anyDensity="true"
|
||||||
|
{% if args.min_sdk_version >= 9 %}
|
||||||
|
android:xlargeScreens="true"
|
||||||
|
{% endif %}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Android 2.3.3 -->
|
||||||
|
<uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ args.sdk_version }}" />
|
||||||
|
|
||||||
|
<!-- OpenGL ES 2.0 -->
|
||||||
|
<uses-feature android:glEsVersion="0x00020000" />
|
||||||
|
|
||||||
|
<!-- Allow writing to external storage -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
{% for perm in args.permissions %}
|
||||||
|
{% if '.' in perm %}
|
||||||
|
<uses-permission android:name="{{ perm }}" />
|
||||||
|
{% else %}
|
||||||
|
<uses-permission android:name="android.permission.{{ perm }}" />
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if args.wakelock %}
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if args.billing_pubkey %}
|
||||||
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Create a Java class extending SDLActivity and place it in a
|
||||||
|
directory under src matching the package, e.g.
|
||||||
|
src/com/gamemaker/game/MyGame.java
|
||||||
|
|
||||||
|
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
|
||||||
|
in the XML below.
|
||||||
|
|
||||||
|
An example Java class can be found in README-android.txt
|
||||||
|
-->
|
||||||
|
<!--application android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:theme="@android:style/Theme.NoTitleBar{% if not args.window %}.Fullscreen{% endif %}"
|
||||||
|
android:hardwareAccelerated="true"-->
|
||||||
|
<application android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light"
|
||||||
|
android:hardwareAccelerated="true">
|
||||||
|
|
||||||
|
{% for m in args.meta_data %}
|
||||||
|
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
|
||||||
|
<meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
|
||||||
|
|
||||||
|
<!--activity android:name="org.kivy.android.PythonActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
|
||||||
|
android:screenOrientation="{{ args.orientation }}"
|
||||||
|
-->
|
||||||
|
<activity android:name="io.lbry.lbrynet.ServiceControlActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
|
||||||
|
android:screenOrientation="{{ args.orientation }}"
|
||||||
|
>
|
||||||
|
|
||||||
|
{% if args.launcher %}
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.kivy.LAUNCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:scheme="{{ url_scheme }}" />
|
||||||
|
</intent-filter>
|
||||||
|
{% else %}
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if args.intent_filters -%}
|
||||||
|
{{- args.intent_filters -}}
|
||||||
|
{%- endif -%}
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
{% if args.launcher %}
|
||||||
|
<activity android:name="org.kivy.android.launcher.ProjectChooser"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if service or args.launcher %}
|
||||||
|
<service android:name="org.kivy.android.PythonService"
|
||||||
|
android:process=":pythonservice" />
|
||||||
|
{% endif %}
|
||||||
|
{% for name in service_names %}
|
||||||
|
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
|
||||||
|
android:process=":service_{{ name }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<service android:name="{{ args.package }}.LbrynetService"
|
||||||
|
android:process=":service_lbrynet" />
|
||||||
|
|
||||||
|
{% if args.billing_pubkey %}
|
||||||
|
<service android:name="org.kivy.android.billing.BillingReceiver"
|
||||||
|
android:process=":pythonbilling" />
|
||||||
|
<receiver android:name="org.kivy.android.billing.BillingReceiver"
|
||||||
|
android:process=":pythonbillingreceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
|
||||||
|
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
|
||||||
|
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
{% endif %}
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,56 @@
|
||||||
|
package {{ args.package }};
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import org.kivy.android.PythonService;
|
||||||
|
import org.kivy.android.PythonActivity;
|
||||||
|
|
||||||
|
|
||||||
|
public class Service{{ name|capitalize }} extends PythonService {
|
||||||
|
{% if sticky %}
|
||||||
|
@Override
|
||||||
|
public int startType() {
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not foreground %}
|
||||||
|
@Override
|
||||||
|
public boolean canDisplayNotification() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStartForeground(Bundle extras) {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Notification notification = new Notification(context.getApplicationInfo().icon,
|
||||||
|
"{{ args.name }}", System.currentTimeMillis());
|
||||||
|
Intent contextIntent = new Intent(context, PythonActivity.class);
|
||||||
|
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
notification.setLatestEventInfo(context, "{{ args.name }}", "{{ name| capitalize }}", pIntent);
|
||||||
|
startForeground({{ service_id }}, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void start(Context ctx, String pythonServiceArgument) {
|
||||||
|
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
|
||||||
|
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
||||||
|
intent.putExtra("androidArgument", argument);
|
||||||
|
intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
|
||||||
|
intent.putExtra("pythonName", "{{ name }}");
|
||||||
|
intent.putExtra("pythonHome", argument);
|
||||||
|
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
||||||
|
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||||
|
ctx.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void stop(Context ctx) {
|
||||||
|
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
|
||||||
|
ctx.stopService(intent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp"
|
||||||
|
tools:context="io.lbry.lbrynet.ServiceControlActivity"
|
||||||
|
>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="5">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="3"
|
||||||
|
android:text="@string/service_status"
|
||||||
|
/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_service_status"
|
||||||
|
android:textColor="@color/red"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:gravity="right"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:text="@string/stopped"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_start_stop"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/start"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,21 @@
|
||||||
|
# This file is used to override default values used by the Ant build system.
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems, as it is
|
||||||
|
# integral to the build system of your project.
|
||||||
|
|
||||||
|
# This file is only used by the Ant script.
|
||||||
|
|
||||||
|
# You can use this to override default values such as
|
||||||
|
# 'source.dir' for the location of your java source folder and
|
||||||
|
# 'out.dir' for the location of your output folder.
|
||||||
|
|
||||||
|
# You can also use it define how the release builds are signed by declaring
|
||||||
|
# the following properties:
|
||||||
|
# 'key.store' for the location of your keystore and
|
||||||
|
# 'key.alias' for the name of the key to use.
|
||||||
|
# The password will be asked during the build when you use the 'release' target.
|
||||||
|
|
||||||
|
key.store=${env.P4A_RELEASE_KEYSTORE}
|
||||||
|
key.alias=${env.P4A_RELEASE_KEYALIAS}
|
||||||
|
key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}
|
||||||
|
key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This should be changed to the name of your project -->
|
||||||
|
<project name="{{ versioned_name }}" default="help">
|
||||||
|
|
||||||
|
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||||
|
It contains the path to the SDK. It should *NOT* be checked into
|
||||||
|
Version Control Systems. -->
|
||||||
|
<property file="local.properties" />
|
||||||
|
|
||||||
|
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||||
|
'android' tool to add properties to it.
|
||||||
|
This is the place to change some Ant specific build properties.
|
||||||
|
Here are some properties you may want to change/update:
|
||||||
|
|
||||||
|
source.dir
|
||||||
|
The name of the source directory. Default is 'src'.
|
||||||
|
out.dir
|
||||||
|
The name of the output directory. Default is 'bin'.
|
||||||
|
|
||||||
|
For other overridable properties, look at the beginning of the rules
|
||||||
|
files in the SDK, at tools/ant/build.xml
|
||||||
|
|
||||||
|
Properties related to the SDK location or the project target should
|
||||||
|
be updated using the 'android' tool with the 'update' action.
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<property file="ant.properties" />
|
||||||
|
|
||||||
|
<!-- if sdk.dir was not set from one of the property file, then
|
||||||
|
get it from the ANDROID_HOME env var.
|
||||||
|
This must be done before we load project.properties since
|
||||||
|
the proguard config can use sdk.dir -->
|
||||||
|
<property environment="env" />
|
||||||
|
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||||
|
<isset property="env.ANDROID_HOME" />
|
||||||
|
</condition>
|
||||||
|
|
||||||
|
<property file="build.properties" />
|
||||||
|
|
||||||
|
<!-- The project.properties file is created and updated by the 'android'
|
||||||
|
tool, as well as ADT.
|
||||||
|
|
||||||
|
This contains project specific properties such as project target, and library
|
||||||
|
dependencies. Lower level build properties are stored in ant.properties
|
||||||
|
(or in .classpath for Eclipse projects).
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems. -->
|
||||||
|
<loadproperties srcFile="project.properties" />
|
||||||
|
|
||||||
|
<!-- quick check on sdk.dir -->
|
||||||
|
<fail
|
||||||
|
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||||
|
unless="sdk.dir"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Import per project custom build rules if present at the root of the project.
|
||||||
|
This is the place to put custom intermediary targets such as:
|
||||||
|
-pre-build
|
||||||
|
-pre-compile
|
||||||
|
-post-compile (This is typically used for code obfuscation.
|
||||||
|
Compiled code location: ${out.classes.absolute.dir}
|
||||||
|
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||||
|
-post-package
|
||||||
|
-post-build
|
||||||
|
-pre-clean
|
||||||
|
-->
|
||||||
|
<import file="custom_rules.xml" optional="true" />
|
||||||
|
|
||||||
|
<!-- Import the actual build file.
|
||||||
|
|
||||||
|
To customize existing targets, there are two options:
|
||||||
|
- Customize only one target:
|
||||||
|
- copy/paste the target into this file, *before* the
|
||||||
|
<import> task.
|
||||||
|
- customize it to your needs.
|
||||||
|
- Customize the whole content of build.xml
|
||||||
|
- copy/paste the content of the rules files (minus the top node)
|
||||||
|
into this file, replacing the <import> task.
|
||||||
|
- customize to your needs.
|
||||||
|
|
||||||
|
***********************
|
||||||
|
****** IMPORTANT ******
|
||||||
|
***********************
|
||||||
|
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||||
|
in order to avoid having your file be overridden by tools such as "android update project"
|
||||||
|
-->
|
||||||
|
<!-- version-tag: 1 -->
|
||||||
|
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project name="CustomRules">
|
||||||
|
<target name="-pre-build">
|
||||||
|
<copy todir="tmp-src">
|
||||||
|
{% if args.launcher %}
|
||||||
|
<fileset dir="src" includes="**" />
|
||||||
|
{% else %}
|
||||||
|
<fileset dir="src">
|
||||||
|
<exclude name="org/kivy/android/ProjectAdapter.java" />
|
||||||
|
<exclude name="org/kivy/android/ProjectChooser.java" />
|
||||||
|
</fileset>
|
||||||
|
{% endif %}
|
||||||
|
{% for dir, includes in args.extra_source_dirs %}
|
||||||
|
<fileset dir="{{ dir }}" includes="{{ includes }}" />
|
||||||
|
{% endfor %}
|
||||||
|
</copy>
|
||||||
|
</target>
|
||||||
|
<target name="-post-build">
|
||||||
|
<delete dir="tmp-src" />
|
||||||
|
</target>
|
||||||
|
</project>
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">{{ args.name }}</string>
|
||||||
|
<string name="private_version">0.1</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>
|
||||||
|
<string name="start">START</string>
|
||||||
|
<string name="stop">STOP</string>
|
||||||
|
</resources>
|
1
p4a/pythonforandroid/bootstraps/lbry/build/whitelist.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# put files here that you need to un-blacklist
|
|
@ -6,7 +6,7 @@ lib_dict = {
|
||||||
'pygame': ['sdl'],
|
'pygame': ['sdl'],
|
||||||
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
||||||
}
|
}
|
||||||
sdl_libs = lib_dict[os.environ['BOOTSTRAP']]
|
sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else []
|
||||||
|
|
||||||
renpy_sound = Extension('android._android_sound',
|
renpy_sound = Extension('android._android_sound',
|
||||||
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
||||||
|
|
|
@ -40,9 +40,10 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
||||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
|
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
|
||||||
is_pygame = bootstrap_name in ('pygame',)
|
is_pygame = bootstrap_name in ('pygame',)
|
||||||
is_webview = bootstrap_name in ('webview',)
|
is_webview = bootstrap_name in ('webview')
|
||||||
|
is_lbry = bootstrap_name in ('lbry')
|
||||||
|
|
||||||
if is_sdl2 or is_webview:
|
if is_sdl2 or is_webview or is_lbry:
|
||||||
if is_sdl2:
|
if is_sdl2:
|
||||||
bootstrap = 'sdl2'
|
bootstrap = 'sdl2'
|
||||||
java_ns = 'org.kivy.android'
|
java_ns = 'org.kivy.android'
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
package io.lbry.lbrynet;
|
package io.lbry.lbrynet;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.File;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import org.kivy.android.PythonService;
|
import org.kivy.android.PythonService;
|
||||||
import org.kivy.android.PythonActivity;
|
import org.kivy.android.PythonActivity;
|
||||||
|
import org.renpy.android.AssetExtract;
|
||||||
|
import org.renpy.android.ResourceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service class is based on the auto-generated P4A service class
|
* This service class is based on the auto-generated P4A service class
|
||||||
|
@ -17,6 +26,9 @@ import org.kivy.android.PythonActivity;
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public class LbrynetService extends PythonService {
|
public class LbrynetService extends PythonService {
|
||||||
|
|
||||||
|
public static String TAG = "LbrynetService";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int startType() {
|
public int startType() {
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
|
@ -27,20 +39,92 @@ public class LbrynetService extends PythonService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Override
|
@Override
|
||||||
protected void doStartForeground(Bundle extras) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
Context context = getApplicationContext();
|
// Extract files
|
||||||
Notification notification = new Notification(context.getApplicationInfo().icon,
|
File app_root_file = new File(getAppRoot());
|
||||||
"lbrydroid", System.currentTimeMillis());
|
unpackData("private", app_root_file);
|
||||||
Intent contextIntent = new Intent(context, PythonActivity.class);
|
|
||||||
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
notification.setLatestEventInfo(context, "lbrydroid", "LbrynetService", pIntent);
|
|
||||||
startForeground(1, notification);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
intent = buildIntent(getApplicationContext(), "");
|
||||||
|
}
|
||||||
|
|
||||||
public static void start(Context ctx, String pythonServiceArgument) {
|
return super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppRoot() {
|
||||||
|
String app_root = getApplicationContext().getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
return app_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recursiveDelete(File f) {
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
for (File r : f.listFiles()) {
|
||||||
|
recursiveDelete(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unpackData(final String resource, File target) {
|
||||||
|
Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
|
||||||
|
|
||||||
|
// The version of data in memory and on disk.
|
||||||
|
ResourceManager resourceManager = new ResourceManager(getApplicationContext());
|
||||||
|
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(getApplicationContext());
|
||||||
|
if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
|
||||||
|
//toastError("Could not extract " + resource + " data.");
|
||||||
|
Log.e(TAG, "Could not extract " + resource + " data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write .nomedia.
|
||||||
|
new File(target, ".nomedia").createNewFile();
|
||||||
|
|
||||||
|
// Write version file.
|
||||||
|
FileOutputStream os = new FileOutputStream(disk_version_fn);
|
||||||
|
os.write(data_version.getBytes());
|
||||||
|
os.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w("python", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent buildIntent(Context ctx, String pythonServiceArgument) {
|
||||||
Intent intent = new Intent(ctx, LbrynetService.class);
|
Intent intent = new Intent(ctx, LbrynetService.class);
|
||||||
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
||||||
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
||||||
|
@ -50,22 +134,17 @@ public class LbrynetService extends PythonService {
|
||||||
intent.putExtra("pythonHome", argument);
|
intent.putExtra("pythonHome", argument);
|
||||||
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
||||||
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start(Context ctx, String pythonServiceArgument) {
|
||||||
|
Intent intent = buildIntent(ctx, pythonServiceArgument);
|
||||||
ctx.startService(intent);
|
ctx.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void stop(Context ctx) {
|
public static void stop(Context ctx) {
|
||||||
Intent intent = new Intent(ctx, ServiceLbrynetservice.class);
|
Intent intent = new Intent(ctx, LbrynetService.class);
|
||||||
ctx.stopService(intent);
|
ctx.stopService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Move to a Utils class
|
|
||||||
*/
|
|
||||||
public static String getAndroidRelease() {
|
|
||||||
return android.os.Build.VERSION.RELEASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getAndroidSdk() {
|
|
||||||
return android.os.Build.VERSION.SDK_INT;
|
|
||||||
}
|
|
||||||
}
|
}
|
89
src/main/java/io/lbry/lbrynet/ServiceControlActivity.java
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package io.lbry.lbrynet;
|
||||||
|
|
||||||
|
import org.kivy.android.PythonActivity;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
//import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class ServiceControlActivity extends Activity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag which indicates whether or not the service is running. Will be updated in the
|
||||||
|
* onResume method.
|
||||||
|
*/
|
||||||
|
private boolean serviceRunning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button which will start or stop the service.
|
||||||
|
*/
|
||||||
|
private Button startStopButton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service status text.
|
||||||
|
*/
|
||||||
|
private TextView serviceStatusText;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_service_control);
|
||||||
|
|
||||||
|
startStopButton = (Button) findViewById(R.id.btn_start_stop);
|
||||||
|
serviceStatusText = (TextView) findViewById(R.id.text_service_status);
|
||||||
|
|
||||||
|
startStopButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (serviceRunning) {
|
||||||
|
LbrynetService.stop(ServiceControlActivity.this);
|
||||||
|
} else {
|
||||||
|
LbrynetService.start(ServiceControlActivity.this, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
updateServiceStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
updateServiceStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateServiceStatus() {
|
||||||
|
new Handler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (serviceRunning) {
|
||||||
|
startStopButton.setText(R.string.stop);
|
||||||
|
serviceStatusText.setText(R.string.running);
|
||||||
|
serviceStatusText.setTextColor(getResources().getColor(R.color.green));
|
||||||
|
} else {
|
||||||
|
startStopButton.setText(R.string.start);
|
||||||
|
serviceStatusText.setText(R.string.stopped);
|
||||||
|
serviceStatusText.setTextColor(getResources().getColor(R.color.red));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||||
|
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||||
|
if (serviceClass.getName().equals(serviceInfo.service.getClassName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/io/lbry/lbrynet/Utils.java
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package io.lbry.lbrynet;
|
||||||
|
|
||||||
|
public final class Utils {
|
||||||
|
public static String getAndroidRelease() {
|
||||||
|
return android.os.Build.VERSION.RELEASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getAndroidSdk() {
|
||||||
|
return android.os.Build.VERSION.SDK_INT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,7 @@ import ssl
|
||||||
# platform.platform() in libc_ver: IOError: [Errno 21] Is a directory
|
# platform.platform() in libc_ver: IOError: [Errno 21] Is a directory
|
||||||
if (kivy.platform == 'android'):
|
if (kivy.platform == 'android'):
|
||||||
from jnius import autoclass
|
from jnius import autoclass
|
||||||
util = autoclass('io.lbry.lbrynet.LbrynetService')
|
util = autoclass('io.lbry.lbrynet.Utils')
|
||||||
platform.platform = lambda: 'Android %s (API %s)' % (util.getAndroidRelease(), util.getAndroidSdk())
|
platform.platform = lambda: 'Android %s (API %s)' % (util.getAndroidRelease(), util.getAndroidSdk())
|
||||||
|
|
||||||
# https certificate verification
|
# https certificate verification
|
||||||
|
|
|
@ -16,11 +16,11 @@ class ServiceApp(App):
|
||||||
LbrynetService = autoclass('io.lbry.lbrynet.LbrynetService')
|
LbrynetService = autoclass('io.lbry.lbrynet.LbrynetService')
|
||||||
context = autoclass('org.kivy.android.PythonActivity').mActivity
|
context = autoclass('org.kivy.android.PythonActivity').mActivity
|
||||||
|
|
||||||
LbrynetService.start(context, '')
|
#LbrynetService.start(context, '')
|
||||||
|
|
||||||
# close the activity once the service starts
|
# close the activity once the service starts
|
||||||
# ideally, we should have some form of service control for the activity
|
# ideally, we should have some form of service control for the activity
|
||||||
context.finish()
|
#context.finish()
|
||||||
|
|
||||||
return Builder.load_string(kv)
|
return Builder.load_string(kv)
|
||||||
|
|
||||||
|
|