From f3600c40beb2f36ea8c34324b821623ea2dc4f8f Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 29 Nov 2022 15:44:04 -0500 Subject: [PATCH] bootstraps lbry --- .../bootstraps/lbry/__init__.py | 136 ++ .../bootstraps/lbry/build/.gitignore | 14 + .../bootstraps/lbry/build/ant.properties | 22 + .../bootstraps/lbry/build/blacklist.txt | 83 + .../bootstraps/lbry/build/build.py | 607 +++++++ .../bootstraps/lbry/build/build.xml | 93 + .../build/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51017 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../bootstraps/lbry/build/gradlew | 164 ++ .../bootstraps/lbry/build/gradlew.bat | 90 + .../bootstraps/lbry/build/jni/Android.mk | 1 + .../bootstraps/lbry/build/jni/Application.mk | 7 + .../lbry/build/jni/application/src/Android.mk | 22 + .../jni/application/src/Android_static.mk | 10 + .../jni/application/src/bootstrap_name.h | 6 + .../build/jni/application/src/pyjniusjni.c | 103 ++ .../lbry/build/proguard-project.txt | 20 + .../build/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 3503 bytes .../build/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 2124 bytes .../build/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 5069 bytes .../build/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 8041 bytes .../build/res/drawable-xxxhdpi/lbry-icon.png | Bin 0 -> 11634 bytes .../lbry/build/res/drawable/.gitkeep | 0 .../lbry/build/res/drawable/icon.png | Bin 0 -> 11634 bytes .../lbry/build/res/layout/chooser_item.xml | 39 + .../bootstraps/lbry/build/res/layout/main.xml | 13 + .../lbry/build/res/layout/project_chooser.xml | 22 + .../lbry/build/res/layout/project_empty.xml | 15 + .../lbry/build/res/values/strings.xml | 5 + .../lbry/build/src/main/assets/.gitkeep | 0 .../lbry/build/src/main/java/.gitkeep | 0 .../main/java/org/kamranzafar/jtar/Octal.java | 141 ++ .../org/kamranzafar/jtar/TarConstants.java | 28 + .../java/org/kamranzafar/jtar/TarEntry.java | 284 +++ .../java/org/kamranzafar/jtar/TarHeader.java | 243 +++ .../org/kamranzafar/jtar/TarInputStream.java | 249 +++ .../org/kamranzafar/jtar/TarOutputStream.java | 163 ++ .../java/org/kamranzafar/jtar/TarUtils.java | 96 + .../android/GenericBroadcastReceiver.java | 19 + .../GenericBroadcastReceiverCallback.java | 8 + .../java/org/kivy/android/PythonActivity.java | 479 +++++ .../java/org/kivy/android/PythonService.java | 138 ++ .../java/org/kivy/android/PythonUtil.java | 68 + .../kivy/android/concurrency/PythonEvent.java | 45 + .../kivy/android/concurrency/PythonLock.java | 19 + .../org/kivy/android/launcher/Project.java | 99 ++ .../kivy/android/launcher/ProjectAdapter.java | 44 + .../kivy/android/launcher/ProjectChooser.java | 94 + .../main/java/org/libsdl/app/SDLActivity.java | 1579 +++++++++++++++++ .../java/org/renpy/android/AssetExtract.java | 116 ++ .../main/java/org/renpy/android/Hardware.java | 287 +++ .../org/renpy/android/PythonActivity.java | 12 + .../java/org/renpy/android/PythonService.java | 12 + .../org/renpy/android/ResourceManager.java | 56 + .../lbry/build/src/main/jniLibs/.gitkeep | 0 .../lbry/build/src/main/libs/.gitkeep | 0 .../baseline_search_black_24.png | Bin 0 -> 386 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 148 bytes .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2683 bytes .../baseline_search_black_24.png | Bin 0 -> 236 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 114 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1698 bytes .../baseline_search_black_24.png | Bin 0 -> 420 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 144 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3872 bytes .../baseline_search_black_24.png | Bin 0 -> 636 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 173 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 6874 bytes .../baseline_search_black_24.png | Bin 0 -> 827 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 209 bytes .../lbry/build/src/main/res/drawable/.gitkeep | 0 .../main/res/drawable/baseline_search_24.xml | 10 + .../src/main/res/layout/chooser_item.xml | 39 + .../lbry/build/src/main/res/layout/main.xml | 13 + .../src/main/res/layout/project_chooser.xml | 22 + .../src/main/res/layout/project_empty.xml | 15 + .../build/templates/AndroidManifest.tmpl.xml | 15 + .../lbry/build/templates/Service.tmpl.java | 77 + .../templates/activity_service_control.xml | 87 + .../lbry/build/templates/build.properties | 21 + .../lbry/build/templates/build.tmpl.gradle | 171 ++ .../build/templates/build.tmpl.gradle.arm | 172 ++ .../lbry/build/templates/build.tmpl.xml | 95 + .../lbry/build/templates/colors.tmpl.xml | 12 + .../build/templates/custom_rules.tmpl.xml | 21 + .../templates/google-services.json.secret | Bin 0 -> 1025 bytes .../lbry/build/templates/gradle.properties | 10 + .../lbry/build/templates/lbry-icon.png | Bin 0 -> 11634 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 148 bytes .../res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2683 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 114 bytes .../res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1698 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 144 bytes .../res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3872 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 173 bytes .../res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 6874 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 209 bytes .../lbry/build/templates/strings.tmpl.xml | 17 + .../lbry/build/templates/themes.tmpl.xml | 21 + .../bootstraps/lbry/build/whitelist.txt | 1 + 100 files changed, 6576 insertions(+) create mode 100644 p4a/pythonforandroid/bootstraps/lbry/__init__.py create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/.gitignore create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/ant.properties create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/blacklist.txt create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/build.py create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/build.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/gradlew create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/Android.mk create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/Application.mk create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/proguard-project.txt create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/drawable/icon.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/layout/chooser_item.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/layout/main.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_chooser.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_empty.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/.gitkeep create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/jniLibs/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/libs/.gitkeep create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/baseline_search_black_24.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/baseline_search_black_24.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/baseline_search_black_24.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/baseline_search_black_24.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/baseline_search_black_24.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep create mode 100755 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/baseline_search_24.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/activity_service_control.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/build.properties create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/colors.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/google-services.json.secret create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/lbry-icon.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/whitelist.txt diff --git a/p4a/pythonforandroid/bootstraps/lbry/__init__.py b/p4a/pythonforandroid/bootstraps/lbry/__init__.py new file mode 100644 index 0000000..0cafc21 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/__init__.py @@ -0,0 +1,136 @@ +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir +from os.path import join, exists, curdir, abspath +from os import walk +import glob +import sh + + +EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx") + + +class LbryBootstrap(Bootstrap): + name = 'lbry' + + recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')] + + def run_distribute(self): + info_main("# Creating Android project ({})".format(self.name)) + + arch = self.ctx.archs[0] + python_install_dir = self.ctx.get_python_install_dir() + from_crystax = self.ctx.python_recipe.from_crystax + crystax_python_dir = join("crystax_python", "crystax_python") + + if len(self.ctx.archs) > 1: + raise ValueError("LBRY/gradle support only one arch") + + info("Copying LBRY/gradle build for {}".format(arch)) + shprint(sh.rm, "-rf", self.dist_dir) + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environemnt variable (ANDROID_HOME) + # or the local.properties if exists + with current_directory(self.dist_dir): + with open('local.properties', 'w') as fileh: + fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) + + with current_directory(self.dist_dir): + info("Copying Python distribution") + + if not exists("private") and not from_crystax: + ensure_dir("private") + if not exists("crystax_python") and from_crystax: + ensure_dir(crystax_python_dir) + + hostpython = sh.Command(self.ctx.hostpython) + if not from_crystax: + try: + shprint(hostpython, '-OO', '-m', 'compileall', + python_install_dir, + _tail=10, _filterout="^Listing") + except sh.ErrorReturnCode: + pass + if not exists('python-install'): + shprint( + sh.cp, '-a', python_install_dir, './python-install') + + self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) + + if not from_crystax: + info("Filling private directory") + if not exists(join("private", "lib")): + info("private/lib does not exist, making") + shprint(sh.cp, "-a", + join("python-install", "lib"), "private") + shprint(sh.mkdir, "-p", + join("private", "include", "python2.7")) + + libpymodules_fn = join("libs", arch.arch, "libpymodules.so") + if exists(libpymodules_fn): + shprint(sh.mv, libpymodules_fn, 'private/') + shprint(sh.cp, + join('python-install', 'include', + 'python2.7', 'pyconfig.h'), + join('private', 'include', 'python2.7/')) + + info('Removing some unwanted files') + shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so')) + 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): + removes = [] + for dirname, root, filenames in walk("."): + for filename in filenames: + for suffix in EXCLUDE_EXTS: + if filename.endswith(suffix): + removes.append(filename) + shprint(sh.rm, '-f', *removes) + + info('Deleting some other stuff not used on android') + # To quote the original distribute.sh, 'well...' + shprint(sh.rm, '-rf', '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') + + 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_dir) + shprint(sh.cp, '-r', join(python_dir, + 'modules'), crystax_python_dir) + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), + join(crystax_python_dir, 'site-packages')) + + info('Renaming .so files to reflect cross-compile') + site_packages_dir = join(crystax_python_dir, "site-packages") + find_ret = shprint( + sh.find, site_packages_dir, '-iname', '*.so') + filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1] + for filename in filenames: + parts = filename.split('.') + if len(parts) <= 2: + continue + shprint(sh.mv, filename, filename.split('.')[0] + '.so') + site_packages_dir = join(abspath(curdir), + site_packages_dir) + if 'sqlite3' not in self.ctx.recipe_build_order: + with open('blacklist.txt', 'a') as fileh: + fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') + + self.strip_libraries(arch) + self.fry_eggs(site_packages_dir) + super(LbryBootstrap, self).run_distribute() + + +bootstrap = LbryBootstrap() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore b/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore new file mode 100644 index 0000000..a1fc39c --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore @@ -0,0 +1,14 @@ +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties b/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties new file mode 100644 index 0000000..0dee5c8 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties @@ -0,0 +1,22 @@ +# 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 + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/blacklist.txt b/p4a/pythonforandroid/bootstraps/lbry/build/blacklist.txt new file mode 100644 index 0000000..72ebeaf --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/blacklist.txt @@ -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 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py new file mode 100755 index 0000000..59857a9 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python2.7 +# coding: utf-8 + +from __future__ import print_function +from os.path import ( + dirname, join, isfile, realpath, relpath, split, exists, basename) +from os import makedirs, remove, listdir +import os +import tarfile +import time +import subprocess +import shutil +from zipfile import ZipFile +import sys +from distutils.version import LooseVersion + +from fnmatch import fnmatch + +import jinja2 + +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 try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + +def render(template, dest, **kwargs): + '''Using jinja2, render `template` to the filename `dest`, supplying the + + 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): + # 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. + try_unlink('src/main/assets/public.mp3') + try_unlink('src/main/assets/private.mp3') + + # In order to speedup import and initial depack, + # construct a python27.zip + make_python_zip() + + # Package up the private data (public not supported). + 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('src/main/assets/private.mp3', tar_dirs, args.ignore_path) + elif args.launcher: + # clean 'None's as a result of main.py path absence + tar_dirs = [tdir for tdir in tar_dirs if tdir] + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) + + # folder name for launcher + url_scheme = 'kivy' + + # Prepare some variables for templating process + default_icon = 'templates/lbry-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png') + shutil.copy(args.presplash or default_presplash, + 'src/main/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, 'src/main/libs') + + # if extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) + + versioned_name = (args.name.replace(' ', '').replace('\'', '') + + '-' + args.version) + + 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/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), + name=name, + entrypoint=entrypoint, + args=args, + foreground=foreground, + sticky=sticky, + service_id=sid + 1) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + build_tools_versions = listdir(join(sdk_dir, 'build-tools')) + build_tools_versions.sort(key=LooseVersion) + build_tools_version = build_tools_versions[-1] + + + render( + 'AndroidManifest.tmpl.xml', + 'src/main/AndroidManifest.xml', + args=args, + service=service, + service_names=service_names, + android_api=android_api, + url_scheme=url_scheme) + + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(join('src', 'main', 'AndroidManifest.xml'), + 'AndroidManifest.xml') + + render( + 'strings.tmpl.xml', + 'src/main/res/values/strings.xml', + args=args, + url_scheme=url_scheme, + private_version=str(time.time())) + + # add colors.xml + render( + 'colors.tmpl.xml', + 'src/main/res/values/colors.xml', + args=args, + url_scheme=url_scheme, + ) + + # add themes.xml + render( + 'themes.tmpl.xml', + 'src/main/res/values/themes.xml', + args=args, + url_scheme=url_scheme, + ) + + # add activity_service_control + render( + 'activity_service_control.xml', + 'src/main/res/layout/activity_service_control.xml', + args=args, + url_scheme=url_scheme, + ) + + ## gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + android_api=android_api, + build_tools_version=build_tools_version) + + render( + 'gradle.properties', + 'gradle.properties', + env=os.environ) + + # copy icon drawables + for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'): + shutil.copy( + 'templates/res/{}/ic_file_download_black_24dp.png'.format(folder), + 'src/main/res/{}/ic_file_download_black_24dp.png'.format(folder) + ); + + ## ant build templates + render( + 'build.tmpl.xml', + 'build.xml', + args=args, + versioned_name=versioned_name) + + render( + 'custom_rules.tmpl.xml', + 'custom_rules.xml', + args=args) + + + if args.sign: + render('build.properties', 'build.properties') + else: + if exists('build.properties'): + os.remove('build.properties') + +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('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + ## The --sdk option has been removed, it is ignored in favour of + ## --android-api handled by toolchain.py + ap.add_argument('--sdk', dest='sdk_version', default=-1, + type=int, help=('Deprecated argument, does nothing')) + ap.add_argument('--minsdk', dest='min_sdk_version', + default=default_android_api, type=int, + help=('Minimum Android SDK version to use. Default to ' + '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('--service', dest='services', action='append', + help='Declare a new service entrypoint: ' + 'NAME:PATH_TO_PY[:foreground]') + ap.add_argument('--add-source', dest='extra_source_dirs', action='append', + help='Include additional source dirs in Java build') + ap.add_argument('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') + ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', + help='Do not optimise .py files to .pyo.') + ap.add_argument('--sign', action='store_true', + 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.sdk_version == -1: + # args.sdk_version = args.min_sdk_version + + if args.sdk_version != -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') + + if args.permissions is None: + args.permissions = [] + 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.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + + if args.no_compile_pyo: + PYTHON = None + BLACKLIST_PATTERNS.remove('*.py') + + if args.blacklist: + with open(args.blacklist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + BLACKLIST_PATTERNS += patterns + + if args.whitelist: + with open(args.whitelist) as fd: + patterns = [x.strip() for x in fd.read().splitlines() + if x.strip() and not x.strip().startswith('#')] + WHITELIST_PATTERNS += patterns + + make_package(args) + + return args + + +if __name__ == "__main__": + parse_args() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.xml b/p4a/pythonforandroid/bootstraps/lbry/build/build.xml new file mode 100644 index 0000000..9f19a07 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..3d0dee6e8edfecc92e04653ec780de06f7b34f8b GIT binary patch literal 51017 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 H-~Rf4?cF@p literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7f81b24 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradlew b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/Android.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/Android.mk new file mode 100644 index 0000000..5053e7d --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/Application.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/Application.mk new file mode 100644 index 0000000..e79e378 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/Application.mk @@ -0,0 +1,7 @@ + +# Uncomment this if you're using STL in your project +# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information +# APP_STL := stlport_static + +# APP_ABI := armeabi armeabi-v7a x86 +APP_ABI := $(ARCH) diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk new file mode 100644 index 0000000..0bc42bf --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +# Add your application source files here... +LOCAL_SRC_FILES := start.c pyjniusjni.c + +LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) + +LOCAL_SHARED_LIBRARIES := python_shared + +LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS) + +LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS) + +include $(BUILD_SHARED_LIBRARY) + +ifdef CRYSTAX_PYTHON_VERSION + $(call import-module,python/$(CRYSTAX_PYTHON_VERSION)) +endif diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk new file mode 100644 index 0000000..2de278e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/Android_static.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +LOCAL_SRC_FILES := YourSourceHere.c + +include $(BUILD_SHARED_LIBRARY) +$(call import-module,SDL)LOCAL_PATH := $(call my-dir) diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h new file mode 100644 index 0000000..b93a4ae --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/bootstrap_name.h @@ -0,0 +1,6 @@ + +#define BOOTSTRAP_NAME_SERVICEONLY +#define BOOTSTRAP_USES_NO_SDL_HEADERS + +const char bootstrap_name[] = "service_only"; + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c new file mode 100644 index 0000000..d67972a --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/jni/application/src/pyjniusjni.c @@ -0,0 +1,103 @@ + +#include +#include + +#define LOGI(...) do {} while (0) +#define LOGE(...) do {} while (0) + +#include "android/log.h" + +/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */ + +/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */ +/* #define LOGP(x) LOG("python", (x)) */ +#define LOG_TAG "Python_android" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + + +/* Function headers */ +JNIEnv* Android_JNI_GetEnv(void); +static void Android_JNI_ThreadDestroyed(void*); + +static pthread_key_t mThreadKey; +static JavaVM* mJavaVM; + +int Android_JNI_SetupThread(void) +{ + Android_JNI_GetEnv(); + return 1; +} + +/* Library init */ +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv *env; + mJavaVM = vm; + LOGI("JNI_OnLoad called"); + if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("Failed to get the environment using GetEnv()"); + return -1; + } + /* + * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread + * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this + */ + if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) { + + __android_log_print(ANDROID_LOG_ERROR, "pyjniusjni", "Error initializing pthread key"); + } + Android_JNI_SetupThread(); + + return JNI_VERSION_1_4; +} + +JNIEnv* Android_JNI_GetEnv(void) +{ + /* From http://developer.android.com/guide/practices/jni.html + * All threads are Linux threads, scheduled by the kernel. + * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then + * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the + * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, + * and cannot make JNI calls. + * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" + * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread + * is a no-op. + * Note: You can call this function any number of times for the same thread, there's no harm in it + */ + + JNIEnv *env; + int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); + if(status < 0) { + LOGE("failed to attach current thread"); + return 0; + } + + /* From http://developer.android.com/guide/practices/jni.html + * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, + * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be + * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific + * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) + * Note: The destructor is not called unless the stored value is != NULL + * Note: You can call this function any number of times for the same thread, there's no harm in it + * (except for some lost CPU cycles) + */ + pthread_setspecific(mThreadKey, (void*) env); + + return env; +} + +static void Android_JNI_ThreadDestroyed(void* value) +{ + /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ + JNIEnv *env = (JNIEnv*) value; + if (env != NULL) { + (*mJavaVM)->DetachCurrentThread(mJavaVM); + pthread_setspecific(mThreadKey, NULL); + } +} + +void *WebView_AndroidGetJNIEnv() +{ + return Android_JNI_GetEnv(); +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/proguard-project.txt b/p4a/pythonforandroid/bootstraps/lbry/build/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7126b0ede6e0bccd9e5768c7abae04b676956a GIT binary patch literal 3503 zcmV;g4N&rlP)@4N26v z?8WdXkrz}5@1&xlvb!MQ1wjZAK~YcAf4=XZnPnDPVVBum;LP*<&+g97{PTbB|J~ki z{uu`cdv1g{GA+ib`NsbLf3tzGkwnuaJS(d!ezx{BqjNePV4w*o9My^=5 z(55xBapz$l1?p|sC-%)Z-=-7N?lgw4@?nJ<1%Q$O!c_A?k?E8Ta-H%n(^Pk*+LQ*| zC)29_G-|L9|HEUr<8i+cV6<;_0$z)6e(g@9_%kQhD#KxxcSCE`6jn$Ij@AP0;k8D9 zp-(g2e+0$ADvLIzF3k;$v~n20x?I zd;q}Q!U1xFwk9y9fHGWDufC0AYHqAH1xH+@=_%8y(+wJvulmDQpqOmj7#P3z3y#sI zAaNH8%U1=+^{Vr9ATTH){EJ{3g<~-Qr97|5OA53JNSx!7YLmyH8gt;Z7hp_klU8+G za%#hKR(0MS`Y<~asUv2caJvOZt1=p+EMG|^#}i($iY6Jr(W;*9P~h-E!T^$=LPC$k zKQAaqoJxk~{}@2cs-uC^1SIL15n_cJGkQbljmi>vw=?jur#Nsh|9;a#p7S$I#S&$j&4aarXBgTV!e+*^{48 zdI31~hTNi)07PYhU?a`lR4@8z$zyz(%>yW3sqTE*(`afK`o#y!F9_LH+Wt4Q9>O7hXgY`Ra#@kq7G25DXr z3EaBmJ(F6*+Mc8AOwv|sn%5g8yFk$2srgJem)`306wqd0{FR)zQ9-ozSINB60~BZ> zopo^5s_lS5Gev*)HjTd~#trPT+#2Tfv_6$6tF)x_b}bc7VPQXb-n(FKN9xK*(XDG_ z;pM~B9EC75@!|Li&v}#k^D!}sUg86BqYZUs0l}FICW64Iujf-$q0uJ`(%qw7Br(!5?e_cPfCrgR2O3QfyeK5cUG zPO`h^Jh^7=X(X5@Hgeh zg!2_Gv{#C@lXOTG%Hg_SmL0Ju+y`Z>P#gQ9#w8|QWTzANw-unbAzT;KSJN3CSGdhC z6k^V$ePlmqt|hgikY8gD0~XyHap9a%xYvWAVueiDGe5+SDfkcBR(+0~2Y-NxpS}1M z`Xz0B6-lnjH#5|yFpm1b_eBoF++7+SHJcO-SvsSyT$@s1ZUn=e8opt!7ZB9z<^r^;;siMjPIwa7 zzde9azy!s>1eL)AZTkJJ#blS4QP+A$M-96dpmm=O1v{)am!P87VC1j|I5;UtLyq6L zLS8N2Aqa7m9NSRuJ#X6M_W(3(&75IE0kp0%kIVpPt*~NAWCL6mqa>Pl)9YZar7)9@ z7y74j|-CnC0T^z6V*akU$|9LqD^%x{y2zfuXr$(jZtM zcng5+nw?D_iLV>{(Q6}k05MU_^TWygA&ON90T4$>qe~Ccke>@CFzZ5An}B+uFpbuU zbil*jGEoA-{{IOy1{20}FhMv`W&?C6zOvt>=e&4Pj1BYd?k=*tB(uYOOFjsr+}zUr zlwh6$HvSrnpu#L7T>BNJ8McvL|e0VmTCl~F8&FzFe-Vl#H?G;j&GyeLRMb~ z(zq998=3o(@}n&PrJ_|r4ADAXedk}mp^!^qlzPfUPJGsx$&w#iH5p zgy=+ob&+auSYRquRHWUB3fD=0L=|_TO9NH~2J_bRvMupg%OH z?<-Hi1ZadQyMpG>5Py6l@m}!O%ZP*#+v4KrIJH z{!|ND?#o5Lm~`M#z_HK)BMJSR8I{P^gX3(k(c;xaO5C}MHXBM&TMJ;jIC{Venn z7c4{UxK>Q*z_$>I%%KCwu~M%VP_xCtn^lrega28S0T}c$)*fR&)IC7`D8 zGz9B4n8bb80#FD9CVJ?Pl7N{%Enp?`-W*8PfYn6bCbw!0e0Zd2hoj+J|(g1pv7j@4T83fZ@|s#lmlF067k`9zU_OiVZUa1-rW zl_<2CHP`U222utDYHh_y8mc!!^x`UlLf_soS!z9-LPjLTY)08x4DNgDxx3G2Q^(;+ z)+pdN*D&KvtuCOqoe{7xCODQAwC7K@#6bqUJz*ls-mL%OZ@x3f;xk(PLNZtHwe@;L zn=V}7$riBr>-`{ee^&OEmcLL0UA*T{iIkpBvY8gyd=V9=Jvl#+0ka}sN14TO(_6Rl z)oV)lgYe-Y+P-WXO` zcU4`V@nJ8bOss5e-`vK^T;XG&=ZBM`LXN~YbU2V$aDw-(Z5TXb!rLIrAXX;9UQ*!L zD_qnIeYf|#DKkS3uVX;MP>FWko_!9Q6$_5n>`AjxhMpq>dkYB;w>k9;q#gSVd~f2k zkbP^eGqck^LE;=TtutqSS36+(i>Nt1uKxaRwBBQ-&B;YN9N&E4{pbs$UJ&hAUX0l` zmJ4WZ(T?&QI;Jzpz1E2-?E}HRGaOUb7oH2U<#Rmq* z5WIJFjqk#@7Q@)^Vcr4kSZ)5+7)J-ONB8cY?zrf2sGA~ajNiu3)5VA5>7$PnD6F+u z(exO~z(gqeUaQItSm*Ld52V>36W9qo60VxZ!G1Ad$ z00m0jz=72k&BAjjC`{OH;4}uWh1c|WBjQaQ1CE6qQ@eTD3Du?^-hJ_HEI!a4@B8vI zll|i7YD9a!yMnh<8nsHz@H##7!V;`~OY>72Snv!D_c1RwQ8LHUbC~n7FG=o3f_>vP z`bIvD&luP0_FekFwfCLqN6_dOIol?w0mVKR`0Mt&Tu;w z4>S`ED+zEco`a58v}a}Mh~x!3u}|!q)fgYVCSDuI&=FoE)CEak4{vYOs6n{=2py)& zp#D#KOdUV%vBmE$_1h4g8n|WgVbRVN1tFQM%EbHEUKJmTyG|Q?56Oxx6Ycu80FUw8 z6rK9mlJ}S5x!4D*E!gK^yar1+JI+g%_$h&dukQnxV6x_iYyXDef;NnP&d$!k&?G2y zPx0_)d&>jIV#Dva5B>|qWAIq^96Wa*_C=Fc=-ZZ|+xlSn`**|H#%%WCAw&8)Jkp~d z)`D5!(8+tUZ7{p9KOTd};yF}PppVW3%KaTX3K|~X)j=$Fb{GrV5bpILZTKDch0}72 dlRX{o{{RP+qAbSCHG2R6002ovPDHLkV1m47#fty{ literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..542c83bc8d182fcb321b583f28a261e04aef0c5d GIT binary patch literal 2124 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 js@@y{l9;CZ&fPgP!8o#4&_h| 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 literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png b/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fb78a16bf69d18e161745816f0d252164560381e GIT binary patch literal 11634 zcmV-&EsfHNP)dfP(hjn2vwx_-bq(@6lu~#5k!jA z6ngI?U`2=YHRO!I-_fJNKM_&R^yqjYeGJ5|_BdB`$G^OI+d- zm$<|wE^&#gOqV28(n!gbTS{3~Fp}1-t6GwwybkxE_Y#-5)C_WfHfHM-8y#qk{$BF? z;5`(A2y%$4!Yq1mKn}1zwky>|yMMk{;(@Gy#GI_K#JsHdCi^m<(B@`7rQM(L93AA} z_#OTq{TuSPcnw~QdlZ)(f)wI1iPeS0ebAqT@0{xpw{N~*!oJLA33-_x5D@p#?#rA( z{=Jj{dn5UoO~wgrUgkgKXFeIX$+%0#Ju)OV?y=wD|K+p4J%QJutiJyw822$e-Z>tHybJ2P&P& zcfxmj)b|&c6*#l99Juc&!qIH9KP!=dWB>upW-@LS0VWDISmE*jdZ~y3QsiVjh!D5v zcO%~s-&N1KE@Do^WvMcUyWmJB=4O0JoX+({fc~x`JJbmHViQ7_XU^-EI${S;Hfx9mcahyj1ELBdJ4d{zqjhm)bus<0Px$S2%{v#e5`uX`xuXJStJ z+X?$Jw+i5wfe3u2n#^V5zRb7yITrqb3avh^r^(G)X#o5J0sK-DLC$sPh=94p9E;UJ z1s4zern%{!$bORXF9-fRG!`zvT=pTa2+F)-jwfKwi+UE~Drc*&%}#%hJdnfsz!w(Z zgwjZv=fjwLu^N=O)>q_C_Iw}m5Hk6wjx~icD>Q_vA2rIvvnc8riOWQiN1L6|Ix#Qv zl#n~>kVSYFcqT#;nMm>|Rt0fsGVzmy+)<}2(&lDPR&ob=kvVadUGl`|q$MWgW$hGJ zU-eG%j=0_Png~f`Ov#gz(Vk4~H68Htgw$zid5dC}40jhKC<*xY z%nRU38474^z5tdPhD&9mrQMslEq24iKtY0HB)7CVsWD{Y4hg+)DdiNhB+~9lJsi7n zQoKl-JtWK-w>SM6GFj&s_-_lzV=dc`x#`WxBwZKa zw{Q~dNxdGoY1->bki&XOaNqJ*O3Y4g!J0;iNRJgnh_t&?^5Zs4ZG~rVwJQPB{FM@N z(q0wdw>}cY@0yn%_1mQX0s~g(7%Si>j?q(k2`&MCt0F<%-*c`6t-k|kVWSSlu##a z7oMB3MSx!no&+(Qr*DtS8th@21$6h@X%MG3W4@3*V(1Nmh4|emQuKx?iwH)l7+}T% zV*L#K#PSd2+Ix!(uNZvB_-_1;xl+`x6UG252H3HHh)-_DhkAWC#e)~4P&qYqilxZa z<36(pr&s}h+}|m21oW46#ckq&i&3N$$|!(85W9KCm52qyA2VwbD2|MF+1MI{+1rHf z7o)i0QSF{oDf+ibJ3~fxan)su&9BCX>bFV>+3Ax6_{AtG3F7{qBSo(sJB#`Q_wi&N z7dw2RQrzyeRzmlSQCfc>cJp*8Z1IRTz*4bJ%T$r{siggLnip|=kLj$xc(7uWR0{1) zlcIi`crt8eFF)BINUo}W=_6m26LQj0g!B=ktoZ>*p~#hEe!QO_P_Y!!)%omBdGr3! zpd#KcMj7%0>`iNn)sE{^0O^W_~@{N$peoxV; z&=j>qg;=~F%+%(jFA~y63{&O?B9@O_Nw8K`mmg4x_OGnnIq$#1@fE|=4spyMQ>5_B zfgb>K6`vuNOI9m+jqGAitp7$~{lzf#Du9a7$d#kFy0>laB(DM$ST@acSY_?*l(xe9 zi%~x9*s%&sl|r)yeF5x+Bq!TesPzxYN_I@hN#8CiJ;f-mh!9-`R*c$Hzt`I?bQP$G zsF2S3@0cU3zZey;3QUng()xc143>XHh*^K%Hf`+42ihdGf9Xad86O;8DP>*EliD6$ zB{j*(6!TKtEtF&xh*&mqyL;27PUXG|uuNR#*zL1LO+U&4~@t z(OXw2@C^qDvE|xnsq3*{rN?u##GE~>P;UEdDSYwJH3W;c<(3yvw14cT=}nb`sm1Z6 z#T)-U@Uyh`$}#D7L4gkNIX5pzeNS$bR$e|V-6*)N`#t_<`}MO@_v61wPv-t8=C8=? za5`#)ZU;^M=K1oA2+3Njigr)RcoF$2-gtso^08;OOaI)?*8%+coqwgt=XOd@=KZAe z2i`ffT>AN+gVHS~i!chv*mL8&)ceFALK+ob1qx#SnjwYGA2f%SOPkscP_%#O!V%u& z100nJkRb^05GVJOBWt9*n-@7i7f9>KDuC7CL9c6ZV2Si2ag49txuqwM4&`5#2A=v; zdWw-o5Fn!f{e&V{j6Uht_v1!#M5w)usR>XeZpZBQy0c-KLn4O$cxC@0Y5t{sQa&eZ zj^DaU_5WkV(DsRp2x%8`qzkui>JjJ&A;QqpTc{%~LZTT`%!bKQ<9U6$1DmFEhUL&F zhE2lWwB^G2EqVal{Z4L@PTsnv1MN*hf{cr~(u?~Smg=7WIj~roac;MCks|^I9O*Gc zT0LjYL}1Qv*utUf2sZ60Mom#3p;-UuwG(3q>#hjrxA1uD;Bp$u(V6RPdrEdzx6r88MIV7a`o720b{#735=RX2FVUa)wmsV zdx?5~{qaoRd}^WL*eakrfBS|s;`CONgfj7dqt9%UjuA=46X6nBA!eQ5BQ+;0hLx=X zcVjkAm4Z`}zo(_rMt6pjW%=XPGSOa}oxV;cK)TgCzliC7d2F4uKmQVq)+llO6_*c7 zuRp}eHD)sH2jUAH%>PIC{xT8rrIZWV^fM${QGHPO!l8e`t%1@jaML-tc(8G#_vg8Y^||>SkQJyR!M(fKTAWAO8w5C z*G@@q6JRB(jHFnJkV)pV&d4;cx_m_X@bE8|msHEkETGY#`QNY6MT3kXgB1BgZtO>v znq$^T0BG&>3%Pnu?&*K8Ndt)NctXWx3dlfzL41_IuASDCY6#1sB;(^FtBD_zWifsL zI>|*WA9>2JcbhPy$sk4k#Qrh)4PEjJy&J8<>SszlkN>V$xCX1gh{%bTOBvIzhyiQg z@#tC_qE%KerQQg?>u_X^+4~4eKhdal6Qo8HziJ1}mc2Fz)pf~9RjK^hF=vQ1g+E&! zTq^x`^|+o>xtC~bls1_2_iA%^rL^YCQN4O2jBVG?NM9XWU#u*(n!};!A{RWj?<86w z3hb7tLX;K0kzp6VCuOxYgg>7qj_kNIe@mA+Cl~)7OMYi5H8@xi-aoWbTK>-==^7{N zup;cbagMHf=2n4xSunTL7kb8@435rLjCL;QfH!%sEn`EG<94Jv~lz+P+< zy>{I5>|i^KEPi`o-$H4@rG0u4lM}bDN?)J&-D*mZ-yo7N^J1 zhe}av#@%f+vP)}VHzc`bsr`UTi~zQAf6eS>iTWSmLOfj5((o~TRA4r%;Fx|ztq7=M zMHX4V0#&YL9e#Q%nU@7BmqD3#Q5_mI?dw6nF2iu8(#8LisD82|4)6?1(*N@2lNHkD zYp3XPuRLTpf*&1TRqmNwHI7&JFP5gA+ofk5l#*^#pkmcjs~-@%Wu_F8(idk6*ij6Z zDjy)fsH%I@Kqu{<)ZZWM3p(hVT*^K@@(5O5E$09kFhDO18f_*a7dGc+>C4HoB%^H7%DV* zC-*@9Kho#N)`@+#r69tPQ-4YOZ(h<17^r0)m4sO2iqY47`n7!#7%q(?T*)`NjUD+} zEqeWgH_dhXBO({DTKoo*D@6LBc3;iToVgGIB^Ih9fD#E>H$iGRp=$?V*sDuR`;t1s z3IXh5H%;kcj*YL{9R7tCuJOw?oL)<kK#m06LnJ70Qa4nF+Lej`0i9p0 zhN`;QEi*=#Q~m$*#3{r6TRzo)ia2c8r<*81YqWvnAY`OU2|!CC8c+LrIxx(zTrvV^ zVyNTb5Vw7Hx;Z$&s9{0J1>Q+RM+R&Nd3paL0a_zUAM`WgdmQZ}{(#B|P!P9mwiJ?* z{4+4juv`KHw4^mac#)%aSIR07AU;RE4sff3OKGni#mPlXKl|nd+GkT(edAV?qQ_AI zS1N+SIus77ZKFI5g=P%=gJ8G@#d6D%>=R}cpc+4yn(=#5|B?xiraA&3ZjTuC6)vWa zf1?YVXeVgQq}U?bv5t?Tp+vj~-TtZ0P7(3DQl!wVfx9)e9cxi6mnak}6QG)Rr`Dd@ zy=mEMBLFhBorpEcA(6#-h~^*BOD3Ese9A2IryzF=kshGI20LyA~2>Wc4| zt&@P|QV}3{W^XMaz;)bAZ3LJ}bf^HXaR!ou8?K(vtBl3YFnO=A`IU+&1c>})^v(Lc z-fvc709yIe?)B)wIlK{H~c*{7RVKIIE%Gi8q)R}E&z5SD7m8h1;U&h?>>i+Hv zz_M4%;%9>^vD9((;90#NC*Z#=2w-^w?Nat}1#RdSvG&T*a%t&UegqhO$LE_5UIvyu z+rL`9xCE%IBfzw;A14y%Uvnk^k~HnfQZA}K<$*BqFPD$#wMRiqMVv`yDM>?x01>~8 zx$V>QU7i5N+80onNg#(tQ+hr|z<<-634mk`qD337o}?{J;Oto=@NpCqDr3>LnokGt79!A? z{^3>94&v1EEgd&qJ4IWgl;)nu1z-dH_op^X?T)Ob1&GMJtHoi~Il(JO-SqDA=5xTZ z=c^@H0TcrGjqDPekduDNoC$z#2YiI%ep2TYt!=SnX#`+q8dSz2rjN?lvKRnlhYgQ* zMQH?l2sML?uvz{v9H;+3Ba%mS1#y01u3Igb1)yH@Q*@Sg`Gk2s$exLd={qwXD zuCZ3TB;rquB>se9>y+QfN}H$(2_%<7gfAL$!u_?!VoFQ^&5Hp@ggH3B)Wj!#cgkK% zApoz7v181zMzBm`}rRuo)w1nD3Cn7F9qP^h^EPBchdhxy^w0kp?A)cLQ?zfA{eemu^bX& zTT%kRX>gC*K6{lV6F>n`>x0W^&AVZbOl+({jf@)i7rb%cXL{fTU#E|54Ulrik~=T} zI0xZ0S0jW~b19iCrJW3_b1_%#$?Bkn9bq5LTOK~xBPG1O2rrw{<$;ew|2S^^9+ELSadDTLAyj{J0sT0d#HS~kV= z1ekMvuSwqv8DvyK^3F1dfGvT^Weg*hWhfe)|4hm;O~&*mkRaoY08Yn2-B6OsYlL+D zLI0R4H5}7rCNRvfTp|KgaeYyXy_Bxu%SLolOIsA40Ln7_mWkV(V3|+9kfY~ZDw}Yi zdCLnB(BKQn%4FEV8mp4A-&0=b@b1sTc z0FR_3EE_Jt8U5ao36N3Yjj^D^(Tqv&;5TBKK!o?`P9@V>Wz^855+YpG3;h*HhX@e{ zo!V@&asq^92b}y3IsmQMx7?@l?SwvF)sdFP{R>59Og<8_Oj)Q%Owl<$vTLuj#h zi35((J{+B<%OZ_HB5XXr!TBFJrpeeHtkE_GQ7+z}*mC`}$>**-HpGW!4LsqT6p8)7 zH8nAwN*P44djnP01khv{{D8V~TW774Wsv&Xqk{;au@eya6#+y*q6-NxQ%WQ3N1l3a zmvn|JJ1Ah?=#YdeNmK^krI*2}l#nFG?qH4H{|Nc=a_8hN`Pf%;E)wL=32Qv_+bsmU z^(lsZi=PC$PXN{zi2SS_vtjadwVw|FX@qj;jn_``Dz#qLG(j?;XVSjpk(!?>h_ub{U3k zN=g7e7vLT^u4{8;XPCL15rBu@?#L=>{ng`o(nvY^(bQQ>fEURs!viHQ+`d6e2N2f8 zij~JT_rk#EHRh?5kYK47vh`L)?E4*gW~;@Wh(obS40ZU@5x47q-s%Hj*Hs%(qa-;1 zJpwTA*|pYNFN70E_@G)_BtEJFv@NXwJ*HRh1F3=P#SSLkB?cl4JH3^LOL?GQzLT#9 z%438mD7i3J$PxF!{f==2#QUwheAuWpLTZvfNY>IKxXk{?U7w3<0_>JF38ohc)p*fJ zkTVL3;{KYsLamN)N%jPQ2z+)Ir1+f?!IbfCNFV5LB+&T%xQO&1(FPyFSSw#EddmHY zLN!7-BFa*C!tn>>1x@RP8XzBvT~8VTs!|#PKr>Zi2aVN@T0MG*x%dH(0?3EN4_IZe zF&wdW5CO8pl<$x5=paS}RGhY@Q7Yrb^d}K{#b>QSc4PVzYS;hK>nBP9BRWq7W*K&? zl{5sZ7YV8v7UlwPI%rDIw+Z;~n3Esy=!Q@$s3b6~_kpw6$;8_waqp%^x<3enXwe~d zV1<%2TTgr)wY4rcbpkAM*{A~VuB|%)vo%6OTuT!H(u)K!C&I(ywWP@So%77n4|o(1 zk#SxA&taMY<$VM+gGJ?|n)d{>IIx7~3Hb0Sl5sdJV1(p!UNu%3?*|JG7F5iEj)G9gP(MS!XrX#5_Shi{Sx)_}uU( z)Od(>xs*WTSfmL(R#W{C%*y(|s-{#Y#Vd}mMq}sHsF6GQ=^2_apgWcsH5A5~a$s}J zl79~BMQI?7P=D0bh?`AHJK)UkynaqEs)Vha@NKNI=Mh%`C;9gI2=RYU$AAD2dQyBT z9AHC7*w#7H7m=VkUSGCOO4vQ`oTbU22dxOu&kGGU$J}8BX*U+xkEks`hm0|pXq zIJeiD&qh${f5PVvzU1Ee+2+7%&9M3|oc=Er0d#%<@cN{=Wu9g!-^mR zA6wKcqV+LoT@Df~x^%$mPr28HKmVE`1x@a;kzmxHVpYa$srA3UAE43L zI|kGN0lY&q2Yy4})Xc-7r5VW3;?&6-2HPcf5IF)On8FIbb+B;ja%@ZNBn|-N<;jVVMIj)^3sX3NY#vRL_mY|ErXh0QW0HU0o3l#ff_jqSj4V%y;0j z#*xT~fT#<*)1%nlVuh{(QVxG}TqOHRE=VsoC z{%w*JG`7nsV3A@H*fe7Fr|6`ZM&sz<>j6i@w?V&;-izNpyFjc0#T(ccjM8LY8X>FP zP6a(R&CdE05`-=I{;v0DuYU$CR`+qQ3v3!A`Sr-8n5G7GhP}vW6#2`jUu9pwIx@q? z+Bb|1^k{SPn2-diA^voL`d=K+hQ_j6;Q}Hy9YTV$?=BBw`g1qg<^2 z#;H={iQWGu*bAl@baeDEmGk>xXIP`DY47O?0{HuV-{G^^Ei>;~dYQBs6<_y5t^dLw z?s;`?)f3nw7_3jbznReaeULNkz?g>*&^Y|(;cKxY2djXH2#H}T>mU95WU0~EuA7+k zug@^(U`prr!I@!=#@W7}EA&@DgYNIXAG>M#zq%B)cxYl6Tl?=#L($v6_1ZT71emk; zboFLkOYF+a`WFg`QU==D`N9_n%^LI*Qq#&Qed581VN3!zEHS&< zwf)G?z`!XzN5yWQ&Nj@X3x`;YvTt}HSc>?0gyi#GTlD)0tX8X*KRx?P#-43?TmPbb zftngOd*lWDZJZq{fk-! zlrx3hlvTjH``c}z)=#<(DI^?XG0Gx+5Lpb%7)p}THp(aRX~hV#rz2YB9@Jm0tSAP1x#sbR12UOE9ba>aU(*=#JOr* z?2#8}XyaZpI(+faJ#v|Z$PI{5ntGopQqZLC`)u55!~;_rXFDH?F&S&+7WXgiDqzd% zF=|!w@BmYTygRpU6TNnPKFTCSR7i{xNuQnb(B|+~oey6A6j&mdYRLAaD>Xl=u>OTB zMdi>`-D>VmjgYMin$%+)vUghK21J>J7{!!6D0cJf`_T+wh+(NN!IBHrr}C%85}*-M?v?6g0KhNtdQ!FUit}VyHI5R5i6ppQ0RMUJ7|ub9WEK zGUnUkolhcHj=l+PC;S01iq!o(=1RdS$@%r#zxI_ZeJFN34Wv(Hjf$_rQb?omvUPL9 zuAR_^!$1E4%Nv|xkslC)2R|)z`)RP$;OqCt0xJwNUgjWu9wvooREQQX+1la@g#}IO zo*K1o0>1b?;S`IZ^q7rUJWTQ*`0)Z@gkr_ct^pVIQN8qeSh1X(Ad{0Ajp5h?&H8Q) z&T@*+PP;4Wg+#B)sg@81*TM6hhL%MfL}rs6SA{w_XC#dfSlb z5Kgfb4@7)ckCg()e7W1HzUN~s7lVYCSrMS9ic#hdxIqeqxIGrtEF^8fKJ4!l zB(P5KNB=re3LM||fT21bG%JsP>Z&0UK#zcLF5wq0&#qkG}7VAE?wOvBt@YH$p*_I($15ODN zSdG(r2bBShr}R8itHl%V00RUIVH){h>skyWcIFWBEU6Sy@&h$lV#8M^{C zNh=T`QVZMF1&4Rt1Sx1z&r>?fFF*SR3=2xK%f`~c|FFnVT}lGk3WV2sG4ZvKIeqqF zqT=_aisCuT(EIn$Bmqa5HX8TULDy%r(EQ=HwOqm(`0H9i#HUQ*Q8N74goL{#gf$DA z_RThg$Kv;--V+&PGtclM$+c{x)Nte%J6)p#U%)fAspX6!H$Q`Lr>&)fzo_u21KZ#r zlfXq@3G8dRCI(LIu{L7)NC~w=qUEA_7Pg_}C4Avf$^VCrzuVVxX-X}>3pI2K_+1$W ztOoo=ZKB~xpz{fAY;2+%4)2^6zGRq0t4IY2%*xqC8$Z9M zOA3DS4C%9!L!FAlE5QE{$5;uG`e>?E3$$@|jvx~m;Tj$EeB%k-e&;2TSPj%;^-;?o zzF>&daA?PkF0qYY!ZRb!E|OIN`*5L7CE6pv{}2)=$uinuf;mp%WKyI2`n8)Fn$iCT zs!6rkX?MkHP_b4YCp99u6+EZ+4WF;ypUU*U;_NnL&#*RordAQ^G*+^#CaViU56A#p z7l$x~1hwCJu5HlNUVG3kkG5UO&AcO4gL1a|WX@srQODnCbeHV9t(tv|XCs53KC1xs z>^!&(uK<4u$|mesQ51SW(&LzjmJC7bMQBSKd;56*K^^9VW(>TEszSL7U4~cN+r)Cfj$%RZ19X&n6n;xc1{MhJp%kCAVD=&7VD~Wjy0M_ieCVW z2YMwP=LRbB;X@N&P+>ENklA7sBSf|v59T)UE8t2vwNSx?jqWl7l|0LA{a^M6DN09 ziiEjsJnicc9~caiVJ)? zR|NRb`NWvq4j9&PeeIS{wa47bR^K@abIz=OefBI|*s~FOzZ@iy5-R0agGRP44&fRH zN&-rRdhK5QveB3>e}?@y=njt8k?V@?6G3#TD+2g?3GjcND1~JWywhmJ=bP)bd8MlY ze9SGKV_EC-R=*}!&?Y2LIfY93)j;tL{E201Onn0fhe)#D@QA# z(|f!;vL-SZ${gb0-hSLIRZ{tuN(3F~St}BS_=ucE zEFB>Q&-(6Sz`zbmYPWj&V`lLcoe#Ma%;b&_n`1XN=fdh&APEe^rAnm8gDuUzG!rTN z2YN(sdosF3gWexb37pg;H*EgkyKtE0RUtzskrXnRGa(E?kMBmTJ&lsVHwvFWq#$rY zx4Z`5d@#-J$%qyPz>j1)zag8;dTef$2#;ZSQ-HsMR|7*?REzTu7!imxa;_ew5W(KX zsi|lC<~;(2c3d7j>)TUk$qoHW!>E)Da_rk&2vMY!C*OgBp7=l5Zi;*fzH7kupDy?O z=+&N>10C=&H?q`GfUoHMT1ChmVf9rSDY9i@I490O@Y1aa8jb8D)T|lJoMlP|m-x_E zd_I3`V8F1>YZNlTagNHV5_DoOlS}TRssSS=`!M_LV+Lv zZ2ayNIqFozY9RkEjS>lZ9r6XQ#mPEYc|bO-Ld0>s8IscPLgT4l=i$Ba8Sq&=UU~9E zd^VJY@bZR!2l8DQ=z}?HugIJ_Y%XfDxe>s(a3UzqvJyV!R|Ri9GC&hEnN;0f&u56@ zENiTths)!TO?6s5+tIsos{y`Wzc(?UfBT;r4*z_0qw!yD4w~F!53FmZX`3Vl}(cI^Xq@VS^ylHFzdT#@3uY@xd=T-pU3W!kU0jq*5)4z4OmBF7m zhC#%FhRTGHr3AIE)pb!No)pT6VIYmSb8&jyA;9~2m+*jBU7Lje*EKP+rE603o32T5 zZ_bQEg9DQY&P*pOgz#ii!iUAm z{h7pQ#K0KDfEi3JvP_T&+0k~0(B&iahWvZw?-;;C={@9o1+jZKV(;b8-rI+L7EccF zZtOGHVBf(I^upSUtJq|)<$MN(5XuVS&SXU$CNb(UFxF?l^kpFR<4B^6h6aQGfBx(> z4cI+=*gd`3d(>s`S&MxJ#lkCqS2Vq?0KK@%N`|5cp{x+qnXGVP0CZ-Kr5gjL2Lows z23UnC%J5`^|DE!8wb^Su*uC7?y`9;6IkESy&RO`P053odfP(hjn2vwx_-bq(@6lu~#5k!jA z6ngI?U`2=YHRO!I-_fJNKM_&R^yqjYeGJ5|_BdB`$G^OI+d- zm$<|wE^&#gOqV28(n!gbTS{3~Fp}1-t6GwwybkxE_Y#-5)C_WfHfHM-8y#qk{$BF? z;5`(A2y%$4!Yq1mKn}1zwky>|yMMk{;(@Gy#GI_K#JsHdCi^m<(B@`7rQM(L93AA} z_#OTq{TuSPcnw~QdlZ)(f)wI1iPeS0ebAqT@0{xpw{N~*!oJLA33-_x5D@p#?#rA( z{=Jj{dn5UoO~wgrUgkgKXFeIX$+%0#Ju)OV?y=wD|K+p4J%QJutiJyw822$e-Z>tHybJ2P&P& zcfxmj)b|&c6*#l99Juc&!qIH9KP!=dWB>upW-@LS0VWDISmE*jdZ~y3QsiVjh!D5v zcO%~s-&N1KE@Do^WvMcUyWmJB=4O0JoX+({fc~x`JJbmHViQ7_XU^-EI${S;Hfx9mcahyj1ELBdJ4d{zqjhm)bus<0Px$S2%{v#e5`uX`xuXJStJ z+X?$Jw+i5wfe3u2n#^V5zRb7yITrqb3avh^r^(G)X#o5J0sK-DLC$sPh=94p9E;UJ z1s4zern%{!$bORXF9-fRG!`zvT=pTa2+F)-jwfKwi+UE~Drc*&%}#%hJdnfsz!w(Z zgwjZv=fjwLu^N=O)>q_C_Iw}m5Hk6wjx~icD>Q_vA2rIvvnc8riOWQiN1L6|Ix#Qv zl#n~>kVSYFcqT#;nMm>|Rt0fsGVzmy+)<}2(&lDPR&ob=kvVadUGl`|q$MWgW$hGJ zU-eG%j=0_Png~f`Ov#gz(Vk4~H68Htgw$zid5dC}40jhKC<*xY z%nRU38474^z5tdPhD&9mrQMslEq24iKtY0HB)7CVsWD{Y4hg+)DdiNhB+~9lJsi7n zQoKl-JtWK-w>SM6GFj&s_-_lzV=dc`x#`WxBwZKa zw{Q~dNxdGoY1->bki&XOaNqJ*O3Y4g!J0;iNRJgnh_t&?^5Zs4ZG~rVwJQPB{FM@N z(q0wdw>}cY@0yn%_1mQX0s~g(7%Si>j?q(k2`&MCt0F<%-*c`6t-k|kVWSSlu##a z7oMB3MSx!no&+(Qr*DtS8th@21$6h@X%MG3W4@3*V(1Nmh4|emQuKx?iwH)l7+}T% zV*L#K#PSd2+Ix!(uNZvB_-_1;xl+`x6UG252H3HHh)-_DhkAWC#e)~4P&qYqilxZa z<36(pr&s}h+}|m21oW46#ckq&i&3N$$|!(85W9KCm52qyA2VwbD2|MF+1MI{+1rHf z7o)i0QSF{oDf+ibJ3~fxan)su&9BCX>bFV>+3Ax6_{AtG3F7{qBSo(sJB#`Q_wi&N z7dw2RQrzyeRzmlSQCfc>cJp*8Z1IRTz*4bJ%T$r{siggLnip|=kLj$xc(7uWR0{1) zlcIi`crt8eFF)BINUo}W=_6m26LQj0g!B=ktoZ>*p~#hEe!QO_P_Y!!)%omBdGr3! zpd#KcMj7%0>`iNn)sE{^0O^W_~@{N$peoxV; z&=j>qg;=~F%+%(jFA~y63{&O?B9@O_Nw8K`mmg4x_OGnnIq$#1@fE|=4spyMQ>5_B zfgb>K6`vuNOI9m+jqGAitp7$~{lzf#Du9a7$d#kFy0>laB(DM$ST@acSY_?*l(xe9 zi%~x9*s%&sl|r)yeF5x+Bq!TesPzxYN_I@hN#8CiJ;f-mh!9-`R*c$Hzt`I?bQP$G zsF2S3@0cU3zZey;3QUng()xc143>XHh*^K%Hf`+42ihdGf9Xad86O;8DP>*EliD6$ zB{j*(6!TKtEtF&xh*&mqyL;27PUXG|uuNR#*zL1LO+U&4~@t z(OXw2@C^qDvE|xnsq3*{rN?u##GE~>P;UEdDSYwJH3W;c<(3yvw14cT=}nb`sm1Z6 z#T)-U@Uyh`$}#D7L4gkNIX5pzeNS$bR$e|V-6*)N`#t_<`}MO@_v61wPv-t8=C8=? za5`#)ZU;^M=K1oA2+3Njigr)RcoF$2-gtso^08;OOaI)?*8%+coqwgt=XOd@=KZAe z2i`ffT>AN+gVHS~i!chv*mL8&)ceFALK+ob1qx#SnjwYGA2f%SOPkscP_%#O!V%u& z100nJkRb^05GVJOBWt9*n-@7i7f9>KDuC7CL9c6ZV2Si2ag49txuqwM4&`5#2A=v; zdWw-o5Fn!f{e&V{j6Uht_v1!#M5w)usR>XeZpZBQy0c-KLn4O$cxC@0Y5t{sQa&eZ zj^DaU_5WkV(DsRp2x%8`qzkui>JjJ&A;QqpTc{%~LZTT`%!bKQ<9U6$1DmFEhUL&F zhE2lWwB^G2EqVal{Z4L@PTsnv1MN*hf{cr~(u?~Smg=7WIj~roac;MCks|^I9O*Gc zT0LjYL}1Qv*utUf2sZ60Mom#3p;-UuwG(3q>#hjrxA1uD;Bp$u(V6RPdrEdzx6r88MIV7a`o720b{#735=RX2FVUa)wmsV zdx?5~{qaoRd}^WL*eakrfBS|s;`CONgfj7dqt9%UjuA=46X6nBA!eQ5BQ+;0hLx=X zcVjkAm4Z`}zo(_rMt6pjW%=XPGSOa}oxV;cK)TgCzliC7d2F4uKmQVq)+llO6_*c7 zuRp}eHD)sH2jUAH%>PIC{xT8rrIZWV^fM${QGHPO!l8e`t%1@jaML-tc(8G#_vg8Y^||>SkQJyR!M(fKTAWAO8w5C z*G@@q6JRB(jHFnJkV)pV&d4;cx_m_X@bE8|msHEkETGY#`QNY6MT3kXgB1BgZtO>v znq$^T0BG&>3%Pnu?&*K8Ndt)NctXWx3dlfzL41_IuASDCY6#1sB;(^FtBD_zWifsL zI>|*WA9>2JcbhPy$sk4k#Qrh)4PEjJy&J8<>SszlkN>V$xCX1gh{%bTOBvIzhyiQg z@#tC_qE%KerQQg?>u_X^+4~4eKhdal6Qo8HziJ1}mc2Fz)pf~9RjK^hF=vQ1g+E&! zTq^x`^|+o>xtC~bls1_2_iA%^rL^YCQN4O2jBVG?NM9XWU#u*(n!};!A{RWj?<86w z3hb7tLX;K0kzp6VCuOxYgg>7qj_kNIe@mA+Cl~)7OMYi5H8@xi-aoWbTK>-==^7{N zup;cbagMHf=2n4xSunTL7kb8@435rLjCL;QfH!%sEn`EG<94Jv~lz+P+< zy>{I5>|i^KEPi`o-$H4@rG0u4lM}bDN?)J&-D*mZ-yo7N^J1 zhe}av#@%f+vP)}VHzc`bsr`UTi~zQAf6eS>iTWSmLOfj5((o~TRA4r%;Fx|ztq7=M zMHX4V0#&YL9e#Q%nU@7BmqD3#Q5_mI?dw6nF2iu8(#8LisD82|4)6?1(*N@2lNHkD zYp3XPuRLTpf*&1TRqmNwHI7&JFP5gA+ofk5l#*^#pkmcjs~-@%Wu_F8(idk6*ij6Z zDjy)fsH%I@Kqu{<)ZZWM3p(hVT*^K@@(5O5E$09kFhDO18f_*a7dGc+>C4HoB%^H7%DV* zC-*@9Kho#N)`@+#r69tPQ-4YOZ(h<17^r0)m4sO2iqY47`n7!#7%q(?T*)`NjUD+} zEqeWgH_dhXBO({DTKoo*D@6LBc3;iToVgGIB^Ih9fD#E>H$iGRp=$?V*sDuR`;t1s z3IXh5H%;kcj*YL{9R7tCuJOw?oL)<kK#m06LnJ70Qa4nF+Lej`0i9p0 zhN`;QEi*=#Q~m$*#3{r6TRzo)ia2c8r<*81YqWvnAY`OU2|!CC8c+LrIxx(zTrvV^ zVyNTb5Vw7Hx;Z$&s9{0J1>Q+RM+R&Nd3paL0a_zUAM`WgdmQZ}{(#B|P!P9mwiJ?* z{4+4juv`KHw4^mac#)%aSIR07AU;RE4sff3OKGni#mPlXKl|nd+GkT(edAV?qQ_AI zS1N+SIus77ZKFI5g=P%=gJ8G@#d6D%>=R}cpc+4yn(=#5|B?xiraA&3ZjTuC6)vWa zf1?YVXeVgQq}U?bv5t?Tp+vj~-TtZ0P7(3DQl!wVfx9)e9cxi6mnak}6QG)Rr`Dd@ zy=mEMBLFhBorpEcA(6#-h~^*BOD3Ese9A2IryzF=kshGI20LyA~2>Wc4| zt&@P|QV}3{W^XMaz;)bAZ3LJ}bf^HXaR!ou8?K(vtBl3YFnO=A`IU+&1c>})^v(Lc z-fvc709yIe?)B)wIlK{H~c*{7RVKIIE%Gi8q)R}E&z5SD7m8h1;U&h?>>i+Hv zz_M4%;%9>^vD9((;90#NC*Z#=2w-^w?Nat}1#RdSvG&T*a%t&UegqhO$LE_5UIvyu z+rL`9xCE%IBfzw;A14y%Uvnk^k~HnfQZA}K<$*BqFPD$#wMRiqMVv`yDM>?x01>~8 zx$V>QU7i5N+80onNg#(tQ+hr|z<<-634mk`qD337o}?{J;Oto=@NpCqDr3>LnokGt79!A? z{^3>94&v1EEgd&qJ4IWgl;)nu1z-dH_op^X?T)Ob1&GMJtHoi~Il(JO-SqDA=5xTZ z=c^@H0TcrGjqDPekduDNoC$z#2YiI%ep2TYt!=SnX#`+q8dSz2rjN?lvKRnlhYgQ* zMQH?l2sML?uvz{v9H;+3Ba%mS1#y01u3Igb1)yH@Q*@Sg`Gk2s$exLd={qwXD zuCZ3TB;rquB>se9>y+QfN}H$(2_%<7gfAL$!u_?!VoFQ^&5Hp@ggH3B)Wj!#cgkK% zApoz7v181zMzBm`}rRuo)w1nD3Cn7F9qP^h^EPBchdhxy^w0kp?A)cLQ?zfA{eemu^bX& zTT%kRX>gC*K6{lV6F>n`>x0W^&AVZbOl+({jf@)i7rb%cXL{fTU#E|54Ulrik~=T} zI0xZ0S0jW~b19iCrJW3_b1_%#$?Bkn9bq5LTOK~xBPG1O2rrw{<$;ew|2S^^9+ELSadDTLAyj{J0sT0d#HS~kV= z1ekMvuSwqv8DvyK^3F1dfGvT^Weg*hWhfe)|4hm;O~&*mkRaoY08Yn2-B6OsYlL+D zLI0R4H5}7rCNRvfTp|KgaeYyXy_Bxu%SLolOIsA40Ln7_mWkV(V3|+9kfY~ZDw}Yi zdCLnB(BKQn%4FEV8mp4A-&0=b@b1sTc z0FR_3EE_Jt8U5ao36N3Yjj^D^(Tqv&;5TBKK!o?`P9@V>Wz^855+YpG3;h*HhX@e{ zo!V@&asq^92b}y3IsmQMx7?@l?SwvF)sdFP{R>59Og<8_Oj)Q%Owl<$vTLuj#h zi35((J{+B<%OZ_HB5XXr!TBFJrpeeHtkE_GQ7+z}*mC`}$>**-HpGW!4LsqT6p8)7 zH8nAwN*P44djnP01khv{{D8V~TW774Wsv&Xqk{;au@eya6#+y*q6-NxQ%WQ3N1l3a zmvn|JJ1Ah?=#YdeNmK^krI*2}l#nFG?qH4H{|Nc=a_8hN`Pf%;E)wL=32Qv_+bsmU z^(lsZi=PC$PXN{zi2SS_vtjadwVw|FX@qj;jn_``Dz#qLG(j?;XVSjpk(!?>h_ub{U3k zN=g7e7vLT^u4{8;XPCL15rBu@?#L=>{ng`o(nvY^(bQQ>fEURs!viHQ+`d6e2N2f8 zij~JT_rk#EHRh?5kYK47vh`L)?E4*gW~;@Wh(obS40ZU@5x47q-s%Hj*Hs%(qa-;1 zJpwTA*|pYNFN70E_@G)_BtEJFv@NXwJ*HRh1F3=P#SSLkB?cl4JH3^LOL?GQzLT#9 z%438mD7i3J$PxF!{f==2#QUwheAuWpLTZvfNY>IKxXk{?U7w3<0_>JF38ohc)p*fJ zkTVL3;{KYsLamN)N%jPQ2z+)Ir1+f?!IbfCNFV5LB+&T%xQO&1(FPyFSSw#EddmHY zLN!7-BFa*C!tn>>1x@RP8XzBvT~8VTs!|#PKr>Zi2aVN@T0MG*x%dH(0?3EN4_IZe zF&wdW5CO8pl<$x5=paS}RGhY@Q7Yrb^d}K{#b>QSc4PVzYS;hK>nBP9BRWq7W*K&? zl{5sZ7YV8v7UlwPI%rDIw+Z;~n3Esy=!Q@$s3b6~_kpw6$;8_waqp%^x<3enXwe~d zV1<%2TTgr)wY4rcbpkAM*{A~VuB|%)vo%6OTuT!H(u)K!C&I(ywWP@So%77n4|o(1 zk#SxA&taMY<$VM+gGJ?|n)d{>IIx7~3Hb0Sl5sdJV1(p!UNu%3?*|JG7F5iEj)G9gP(MS!XrX#5_Shi{Sx)_}uU( z)Od(>xs*WTSfmL(R#W{C%*y(|s-{#Y#Vd}mMq}sHsF6GQ=^2_apgWcsH5A5~a$s}J zl79~BMQI?7P=D0bh?`AHJK)UkynaqEs)Vha@NKNI=Mh%`C;9gI2=RYU$AAD2dQyBT z9AHC7*w#7H7m=VkUSGCOO4vQ`oTbU22dxOu&kGGU$J}8BX*U+xkEks`hm0|pXq zIJeiD&qh${f5PVvzU1Ee+2+7%&9M3|oc=Er0d#%<@cN{=Wu9g!-^mR zA6wKcqV+LoT@Df~x^%$mPr28HKmVE`1x@a;kzmxHVpYa$srA3UAE43L zI|kGN0lY&q2Yy4})Xc-7r5VW3;?&6-2HPcf5IF)On8FIbb+B;ja%@ZNBn|-N<;jVVMIj)^3sX3NY#vRL_mY|ErXh0QW0HU0o3l#ff_jqSj4V%y;0j z#*xT~fT#<*)1%nlVuh{(QVxG}TqOHRE=VsoC z{%w*JG`7nsV3A@H*fe7Fr|6`ZM&sz<>j6i@w?V&;-izNpyFjc0#T(ccjM8LY8X>FP zP6a(R&CdE05`-=I{;v0DuYU$CR`+qQ3v3!A`Sr-8n5G7GhP}vW6#2`jUu9pwIx@q? z+Bb|1^k{SPn2-diA^voL`d=K+hQ_j6;Q}Hy9YTV$?=BBw`g1qg<^2 z#;H={iQWGu*bAl@baeDEmGk>xXIP`DY47O?0{HuV-{G^^Ei>;~dYQBs6<_y5t^dLw z?s;`?)f3nw7_3jbznReaeULNkz?g>*&^Y|(;cKxY2djXH2#H}T>mU95WU0~EuA7+k zug@^(U`prr!I@!=#@W7}EA&@DgYNIXAG>M#zq%B)cxYl6Tl?=#L($v6_1ZT71emk; zboFLkOYF+a`WFg`QU==D`N9_n%^LI*Qq#&Qed581VN3!zEHS&< zwf)G?z`!XzN5yWQ&Nj@X3x`;YvTt}HSc>?0gyi#GTlD)0tX8X*KRx?P#-43?TmPbb zftngOd*lWDZJZq{fk-! zlrx3hlvTjH``c}z)=#<(DI^?XG0Gx+5Lpb%7)p}THp(aRX~hV#rz2YB9@Jm0tSAP1x#sbR12UOE9ba>aU(*=#JOr* z?2#8}XyaZpI(+faJ#v|Z$PI{5ntGopQqZLC`)u55!~;_rXFDH?F&S&+7WXgiDqzd% zF=|!w@BmYTygRpU6TNnPKFTCSR7i{xNuQnb(B|+~oey6A6j&mdYRLAaD>Xl=u>OTB zMdi>`-D>VmjgYMin$%+)vUghK21J>J7{!!6D0cJf`_T+wh+(NN!IBHrr}C%85}*-M?v?6g0KhNtdQ!FUit}VyHI5R5i6ppQ0RMUJ7|ub9WEK zGUnUkolhcHj=l+PC;S01iq!o(=1RdS$@%r#zxI_ZeJFN34Wv(Hjf$_rQb?omvUPL9 zuAR_^!$1E4%Nv|xkslC)2R|)z`)RP$;OqCt0xJwNUgjWu9wvooREQQX+1la@g#}IO zo*K1o0>1b?;S`IZ^q7rUJWTQ*`0)Z@gkr_ct^pVIQN8qeSh1X(Ad{0Ajp5h?&H8Q) z&T@*+PP;4Wg+#B)sg@81*TM6hhL%MfL}rs6SA{w_XC#dfSlb z5Kgfb4@7)ckCg()e7W1HzUN~s7lVYCSrMS9ic#hdxIqeqxIGrtEF^8fKJ4!l zB(P5KNB=re3LM||fT21bG%JsP>Z&0UK#zcLF5wq0&#qkG}7VAE?wOvBt@YH$p*_I($15ODN zSdG(r2bBShr}R8itHl%V00RUIVH){h>skyWcIFWBEU6Sy@&h$lV#8M^{C zNh=T`QVZMF1&4Rt1Sx1z&r>?fFF*SR3=2xK%f`~c|FFnVT}lGk3WV2sG4ZvKIeqqF zqT=_aisCuT(EIn$Bmqa5HX8TULDy%r(EQ=HwOqm(`0H9i#HUQ*Q8N74goL{#gf$DA z_RThg$Kv;--V+&PGtclM$+c{x)Nte%J6)p#U%)fAspX6!H$Q`Lr>&)fzo_u21KZ#r zlfXq@3G8dRCI(LIu{L7)NC~w=qUEA_7Pg_}C4Avf$^VCrzuVVxX-X}>3pI2K_+1$W ztOoo=ZKB~xpz{fAY;2+%4)2^6zGRq0t4IY2%*xqC8$Z9M zOA3DS4C%9!L!FAlE5QE{$5;uG`e>?E3$$@|jvx~m;Tj$EeB%k-e&;2TSPj%;^-;?o zzF>&daA?PkF0qYY!ZRb!E|OIN`*5L7CE6pv{}2)=$uinuf;mp%WKyI2`n8)Fn$iCT zs!6rkX?MkHP_b4YCp99u6+EZ+4WF;ypUU*U;_NnL&#*RordAQ^G*+^#CaViU56A#p z7l$x~1hwCJu5HlNUVG3kkG5UO&AcO4gL1a|WX@srQODnCbeHV9t(tv|XCs53KC1xs z>^!&(uK<4u$|mesQ51SW(&LzjmJC7bMQBSKd;56*K^^9VW(>TEszSL7U4~cN+r)Cfj$%RZ19X&n6n;xc1{MhJp%kCAVD=&7VD~Wjy0M_ieCVW z2YMwP=LRbB;X@N&P+>ENklA7sBSf|v59T)UE8t2vwNSx?jqWl7l|0LA{a^M6DN09 ziiEjsJnicc9~caiVJ)? zR|NRb`NWvq4j9&PeeIS{wa47bR^K@abIz=OefBI|*s~FOzZ@iy5-R0agGRP44&fRH zN&-rRdhK5QveB3>e}?@y=njt8k?V@?6G3#TD+2g?3GjcND1~JWywhmJ=bP)bd8MlY ze9SGKV_EC-R=*}!&?Y2LIfY93)j;tL{E201Onn0fhe)#D@QA# z(|f!;vL-SZ${gb0-hSLIRZ{tuN(3F~St}BS_=ucE zEFB>Q&-(6Sz`zbmYPWj&V`lLcoe#Ma%;b&_n`1XN=fdh&APEe^rAnm8gDuUzG!rTN z2YN(sdosF3gWexb37pg;H*EgkyKtE0RUtzskrXnRGa(E?kMBmTJ&lsVHwvFWq#$rY zx4Z`5d@#-J$%qyPz>j1)zag8;dTef$2#;ZSQ-HsMR|7*?REzTu7!imxa;_ew5W(KX zsi|lC<~;(2c3d7j>)TUk$qoHW!>E)Da_rk&2vMY!C*OgBp7=l5Zi;*fzH7kupDy?O z=+&N>10C=&H?q`GfUoHMT1ChmVf9rSDY9i@I490O@Y1aa8jb8D)T|lJoMlP|m-x_E zd_I3`V8F1>YZNlTagNHV5_DoOlS}TRssSS=`!M_LV+Lv zZ2ayNIqFozY9RkEjS>lZ9r6XQ#mPEYc|bO-Ld0>s8IscPLgT4l=i$Ba8Sq&=UU~9E zd^VJY@bZR!2l8DQ=z}?HugIJ_Y%XfDxe>s(a3UzqvJyV!R|Ri9GC&hEnN;0f&u56@ zENiTths)!TO?6s5+tIsos{y`Wzc(?UfBT;r4*z_0qw!yD4w~F!53FmZX`3Vl}(cI^Xq@VS^ylHFzdT#@3uY@xd=T-pU3W!kU0jq*5)4z4OmBF7m zhC#%FhRTGHr3AIE)pb!No)pT6VIYmSb8&jyA;9~2m+*jBU7Lje*EKP+rE603o32T5 zZ_bQEg9DQY&P*pOgz#ii!iUAm z{h7pQ#K0KDfEi3JvP_T&+0k~0(B&iahWvZw?-;;C={@9o1+jZKV(;b8-rI+L7EccF zZtOGHVBf(I^upSUtJq|)<$MN(5XuVS&SXU$CNb(UFxF?l^kpFR<4B^6h6aQGfBx(> z4cI+=*gd`3d(>s`S&MxJ#lkCqS2Vq?0KK@%N`|5cp{x+qnXGVP0CZ-Kr5gjL2Lows z23UnC%J5`^|DE!8wb^Su*uC7?y`9;6IkESy&RO`P053o + + + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/main.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/main.xml new file mode 100644 index 0000000..123c4b6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_chooser.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_chooser.xml new file mode 100644 index 0000000..23828e6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_chooser.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_empty.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_empty.xml new file mode 100644 index 0000000..ee54814 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/layout/project_empty.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml new file mode 100644 index 0000000..daebceb --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml @@ -0,0 +1,5 @@ + + + SDL App + 0.1 + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java new file mode 100755 index 0000000..dd10624 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java @@ -0,0 +1,141 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class Octal { + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal string. + */ + public static long parseOctal(byte[] header, int offset, int length) { + long result = 0; + boolean stillPadding = true; + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + + if (header[i] == (byte) ' ' || header[i] == '0') { + if (stillPadding) + continue; + + if (header[i] == (byte) ' ') + break; + } + + stillPadding = false; + + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes(long value, byte[] buf, int offset, int length) { + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = (byte) ' '; + --idx; + + if (value == 0) { + buf[offset + idx] = (byte) '0'; + --idx; + } else { + for (long val = value; idx >= 0 && val > 0; --idx) { + buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) ); + val = val >> 3; + } + } + + for (; idx >= 0; --idx) { + buf[offset + idx] = (byte) ' '; + } + + return offset + length; + } + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) { + getOctalBytes( value, buf, offset, length ); + buf[offset + length - 1] = (byte) ' '; + buf[offset + length - 2] = 0; + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) { + byte[] temp = new byte[length + 1]; + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + return offset + length; + } + +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java new file mode 100755 index 0000000..4611e20 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java @@ -0,0 +1,28 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class TarConstants { + public static final int EOF_BLOCK = 1024; + public static final int DATA_BLOCK = 512; + public static final int HEADER_BLOCK = 512; +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java new file mode 100755 index 0000000..fe01db4 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java @@ -0,0 +1,284 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; +import java.util.Date; + +/** + * @author Kamran Zafar + * + */ +public class TarEntry { + protected File file; + protected TarHeader header; + + private TarEntry() { + this.file = null; + header = new TarHeader(); + } + + public TarEntry(File file, String entryName) { + this(); + this.file = file; + this.extractTarHeader(entryName); + } + + public TarEntry(byte[] headerBuf) { + this(); + this.parseTarHeader(headerBuf); + } + + /** + * Constructor to create an entry from an existing TarHeader object. + * + * This method is useful to add new entries programmatically (e.g. for + * adding files or directories that do not exist in the file system). + * + * @param header + * + */ + public TarEntry(TarHeader header) { + this.file = null; + this.header = header; + } + + public boolean equals(TarEntry it) { + return header.name.toString().equals(it.header.name.toString()); + } + + public boolean isDescendent(TarEntry desc) { + return desc.header.name.toString().startsWith(header.name.toString()); + } + + public TarHeader getHeader() { + return header; + } + + public String getName() { + String name = header.name.toString(); + if (header.namePrefix != null && !header.namePrefix.toString().equals("")) { + name = header.namePrefix.toString() + "/" + name; + } + + return name; + } + + public void setName(String name) { + header.name = new StringBuffer(name); + } + + public int getUserId() { + return header.userId; + } + + public void setUserId(int userId) { + header.userId = userId; + } + + public int getGroupId() { + return header.groupId; + } + + public void setGroupId(int groupId) { + header.groupId = groupId; + } + + public String getUserName() { + return header.userName.toString(); + } + + public void setUserName(String userName) { + header.userName = new StringBuffer(userName); + } + + public String getGroupName() { + return header.groupName.toString(); + } + + public void setGroupName(String groupName) { + header.groupName = new StringBuffer(groupName); + } + + public void setIds(int userId, int groupId) { + this.setUserId(userId); + this.setGroupId(groupId); + } + + public void setModTime(long time) { + header.modTime = time / 1000; + } + + public void setModTime(Date time) { + header.modTime = time.getTime() / 1000; + } + + public Date getModTime() { + return new Date(header.modTime * 1000); + } + + public File getFile() { + return this.file; + } + + public long getSize() { + return header.size; + } + + public void setSize(long size) { + header.size = size; + } + + /** + * Checks if the org.kamrazafar.jtar entry is a directory + * + * @return + */ + public boolean isDirectory() { + if (this.file != null) + return this.file.isDirectory(); + + if (header != null) { + if (header.linkFlag == TarHeader.LF_DIR) + return true; + + if (header.name.toString().endsWith("/")) + return true; + } + + return false; + } + + /** + * Extract header from File + * + * @param entryName + */ + public void extractTarHeader(String entryName) { + header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory()); + } + + /** + * Calculate checksum + * + * @param buf + * @return + */ + public long computeCheckSum(byte[] buf) { + long sum = 0; + + for (int i = 0; i < buf.length; ++i) { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Writes the header to the byte buffer + * + * @param outbuf + */ + public void writeEntryHeader(byte[] outbuf) { + int offset = 0; + + offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN); + offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN); + offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN); + offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN); + + long size = header.size; + + offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN); + offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN); + + int csOffset = offset; + for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) + outbuf[offset++] = (byte) ' '; + + outbuf[offset++] = header.linkFlag; + + offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN); + offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN); + offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN); + offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX); + + for (; offset < outbuf.length;) + outbuf[offset++] = 0; + + long checkSum = this.computeCheckSum(outbuf); + + Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN); + } + + /** + * Parses the tar header to the byte buffer + * + * @param header + * @param bh + */ + public void parseTarHeader(byte[] bh) { + int offset = 0; + + header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN); + offset += TarHeader.MODELEN; + + header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN); + offset += TarHeader.UIDLEN; + + header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN); + offset += TarHeader.GIDLEN; + + header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN); + offset += TarHeader.SIZELEN; + + header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN); + offset += TarHeader.MODTIMELEN; + + header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN); + offset += TarHeader.CHKSUMLEN; + + header.linkFlag = bh[offset++]; + + header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN); + offset += TarHeader.USTAR_MAGICLEN; + + header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN); + offset += TarHeader.USTAR_USER_NAMELEN; + + header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset += TarHeader.USTAR_GROUP_NAMELEN; + + header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX); + } +} \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java new file mode 100755 index 0000000..b9d3a86 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java @@ -0,0 +1,243 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * Header + * + *
+ * Offset  Size     Field
+ * 0       100      File name
+ * 100     8        File mode
+ * 108     8        Owner's numeric user ID
+ * 116     8        Group's numeric user ID
+ * 124     12       File size in bytes
+ * 136     12       Last modification time in numeric Unix time format
+ * 148     8        Checksum for header block
+ * 156     1        Link indicator (file type)
+ * 157     100      Name of linked file
+ * 
+ * + * + * File Types + * + *
+ * Value        Meaning
+ * '0'          Normal file
+ * (ASCII NUL)  Normal file (now obsolete)
+ * '1'          Hard link
+ * '2'          Symbolic link
+ * '3'          Character special
+ * '4'          Block special
+ * '5'          Directory
+ * '6'          FIFO
+ * '7'          Contigous
+ * 
+ * + * + * + * Ustar header + * + *
+ * Offset  Size    Field
+ * 257     6       UStar indicator "ustar"
+ * 263     2       UStar version "00"
+ * 265     32      Owner user name
+ * 297     32      Owner group name
+ * 329     8       Device major number
+ * 337     8       Device minor number
+ * 345     155     Filename prefix
+ * 
+ */ + +public class TarHeader { + + /* + * Header + */ + public static final int NAMELEN = 100; + public static final int MODELEN = 8; + public static final int UIDLEN = 8; + public static final int GIDLEN = 8; + public static final int SIZELEN = 12; + public static final int MODTIMELEN = 12; + public static final int CHKSUMLEN = 8; + public static final byte LF_OLDNORM = 0; + + /* + * File Types + */ + public static final byte LF_NORMAL = (byte) '0'; + public static final byte LF_LINK = (byte) '1'; + public static final byte LF_SYMLINK = (byte) '2'; + public static final byte LF_CHR = (byte) '3'; + public static final byte LF_BLK = (byte) '4'; + public static final byte LF_DIR = (byte) '5'; + public static final byte LF_FIFO = (byte) '6'; + public static final byte LF_CONTIG = (byte) '7'; + + /* + * Ustar header + */ + + public static final String USTAR_MAGIC = "ustar"; // POSIX + + public static final int USTAR_MAGICLEN = 8; + public static final int USTAR_USER_NAMELEN = 32; + public static final int USTAR_GROUP_NAMELEN = 32; + public static final int USTAR_DEVLEN = 8; + public static final int USTAR_FILENAME_PREFIX = 155; + + // Header values + public StringBuffer name; + public int mode; + public int userId; + public int groupId; + public long size; + public long modTime; + public int checkSum; + public byte linkFlag; + public StringBuffer linkName; + public StringBuffer magic; // ustar indicator and version + public StringBuffer userName; + public StringBuffer groupName; + public int devMajor; + public int devMinor; + public StringBuffer namePrefix; + + public TarHeader() { + this.magic = new StringBuffer(TarHeader.USTAR_MAGIC); + + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty("user.name", ""); + + if (user.length() > 31) + user = user.substring(0, 31); + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer(user); + this.groupName = new StringBuffer(""); + this.namePrefix = new StringBuffer(); + } + + /** + * Parse an entry name from a header buffer. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName(byte[] header, int offset, int length) { + StringBuffer result = new StringBuffer(length); + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + result.append((char) header[i]); + } + + return result; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) { + int i; + + for (i = 0; i < length && i < name.length(); ++i) { + buf[offset + i] = (byte) name.charAt(i); + } + + for (; i < length; ++i) { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Creates a new header for a file/directory entry. + * + * + * @param name + * File name + * @param size + * File size in bytes + * @param modTime + * Last modification time in numeric Unix time format + * @param dir + * Is directory + * + * @return + */ + public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) { + String name = entryName; + name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/'); + + TarHeader header = new TarHeader(); + header.linkName = new StringBuffer(""); + + if (name.length() > 100) { + header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/'))); + header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1)); + } else { + header.name = new StringBuffer(name); + } + + if (dir) { + header.mode = 040755; + header.linkFlag = TarHeader.LF_DIR; + if (header.name.charAt(header.name.length() - 1) != '/') { + header.name.append("/"); + } + header.size = 0; + } else { + header.mode = 0100644; + header.linkFlag = TarHeader.LF_NORMAL; + header.size = size; + } + + header.modTime = modTime; + header.checkSum = 0; + header.devMajor = 0; + header.devMinor = 0; + + return header; + } +} \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java new file mode 100755 index 0000000..ec50a1b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java @@ -0,0 +1,249 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Kamran Zafar + * + */ +public class TarInputStream extends FilterInputStream { + + private static final int SKIP_BUFFER_SIZE = 2048; + private TarEntry currentEntry; + private long currentFileSize; + private long bytesRead; + private boolean defaultSkip = false; + + public TarInputStream(InputStream in) { + super(in); + currentFileSize = 0; + bytesRead = 0; + } + + @Override + public boolean markSupported() { + return false; + } + + /** + * Not supported + * + */ + @Override + public synchronized void mark(int readlimit) { + } + + /** + * Not supported + * + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + /** + * Read a byte + * + * @see java.io.FilterInputStream#read() + */ + @Override + public int read() throws IOException { + byte[] buf = new byte[1]; + + int res = this.read(buf, 0, 1); + + if (res != -1) { + return 0xFF & buf[0]; + } + + return res; + } + + /** + * Checks if the bytes being read exceed the entry size and adjusts the byte + * array length. Updates the byte counters + * + * + * @see java.io.FilterInputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (currentEntry != null) { + if (currentFileSize == currentEntry.getSize()) { + return -1; + } else if ((currentEntry.getSize() - currentFileSize) < len) { + len = (int) (currentEntry.getSize() - currentFileSize); + } + } + + int br = super.read(b, off, len); + + if (br != -1) { + if (currentEntry != null) { + currentFileSize += br; + } + + bytesRead += br; + } + + return br; + } + + /** + * Returns the next entry in the tar file + * + * @return TarEntry + * @throws IOException + */ + public TarEntry getNextEntry() throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + byte[] theader = new byte[TarConstants.HEADER_BLOCK]; + int tr = 0; + + // Read full header + while (tr < TarConstants.HEADER_BLOCK) { + int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr); + + if (res < 0) { + break; + } + + System.arraycopy(theader, 0, header, tr, res); + tr += res; + } + + // Check if record is null + boolean eof = true; + for (byte b : header) { + if (b != 0) { + eof = false; + break; + } + } + + if (!eof) { + currentEntry = new TarEntry(header); + } + + return currentEntry; + } + + /** + * Returns the current offset (in bytes) from the beginning of the stream. + * This can be used to find out at which point in a tar file an entry's content begins, for instance. + */ + public long getCurrentOffset() { + return bytesRead; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + // Not fully read, skip rest of the bytes + long bs = 0; + while (bs < currentEntry.getSize() - currentFileSize) { + long res = skip(currentEntry.getSize() - currentFileSize - bs); + + if (res == 0 && currentEntry.getSize() - currentFileSize > 0) { + // I suspect file corruption + throw new IOException("Possible tar file corruption"); + } + + bs += res; + } + } + + currentEntry = null; + currentFileSize = 0L; + skipPad(); + } + } + + /** + * Skips the pad at the end of each tar entry file content + * + * @throws IOException + */ + protected void skipPad() throws IOException { + if (bytesRead > 0) { + int extra = (int) (bytesRead % TarConstants.DATA_BLOCK); + + if (extra > 0) { + long bs = 0; + while (bs < TarConstants.DATA_BLOCK - extra) { + long res = skip(TarConstants.DATA_BLOCK - extra - bs); + bs += res; + } + } + } + } + + /** + * Skips 'n' bytes on the InputStream
+ * Overrides default implementation of skip + * + */ + @Override + public long skip(long n) throws IOException { + if (defaultSkip) { + // use skip method of parent stream + // may not work if skip not implemented by parent + long bs = super.skip(n); + bytesRead += bs; + + return bs; + } + + if (n <= 0) { + return 0; + } + + long left = n; + byte[] sBuff = new byte[SKIP_BUFFER_SIZE]; + + while (left > 0) { + int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE)); + if (res < 0) { + break; + } + left -= res; + } + + return n - left; + } + + public boolean isDefaultSkip() { + return defaultSkip; + } + + public void setDefaultSkip(boolean defaultSkip) { + this.defaultSkip = defaultSkip; + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java new file mode 100755 index 0000000..ffdfe87 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java @@ -0,0 +1,163 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +/** + * @author Kamran Zafar + * + */ +public class TarOutputStream extends OutputStream { + private final OutputStream out; + private long bytesWritten; + private long currentFileSize; + private TarEntry currentEntry; + + public TarOutputStream(OutputStream out) { + this.out = out; + bytesWritten = 0; + currentFileSize = 0; + } + + public TarOutputStream(final File fout) throws FileNotFoundException { + this.out = new BufferedOutputStream(new FileOutputStream(fout)); + bytesWritten = 0; + currentFileSize = 0; + } + + /** + * Opens a file for writing. + */ + public TarOutputStream(final File fout, final boolean append) throws IOException { + @SuppressWarnings("resource") + RandomAccessFile raf = new RandomAccessFile(fout, "rw"); + final long fileSize = fout.length(); + if (append && fileSize > TarConstants.EOF_BLOCK) { + raf.seek(fileSize - TarConstants.EOF_BLOCK); + } + out = new BufferedOutputStream(new FileOutputStream(raf.getFD())); + } + + /** + * Appends the EOF record and closes the stream + * + * @see java.io.FilterOutputStream#close() + */ + @Override + public void close() throws IOException { + closeCurrentEntry(); + write( new byte[TarConstants.EOF_BLOCK] ); + out.close(); + } + /** + * Writes a byte to the stream and updates byte counters + * + * @see java.io.FilterOutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + out.write( b ); + bytesWritten += 1; + + if (currentEntry != null) { + currentFileSize += 1; + } + } + + /** + * Checks if the bytes being written exceed the current entry size. + * + * @see java.io.FilterOutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (currentEntry != null && !currentEntry.isDirectory()) { + if (currentEntry.getSize() < currentFileSize + len) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] size[" + + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len ) + + "] being written." ); + } + } + + out.write( b, off, len ); + + bytesWritten += len; + + if (currentEntry != null) { + currentFileSize += len; + } + } + + /** + * Writes the next tar entry header on the stream + * + * @param entry + * @throws IOException + */ + public void putNextEntry(TarEntry entry) throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + entry.writeEntryHeader( header ); + + write( header ); + + currentEntry = entry; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] of size[" + + currentEntry.getSize() + "] has not been fully written." ); + } + + currentEntry = null; + currentFileSize = 0; + + pad(); + } + } + + /** + * Pads the last content block + * + * @throws IOException + */ + protected void pad() throws IOException { + if (bytesWritten > 0) { + int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK ); + + if (extra > 0) { + write( new byte[TarConstants.DATA_BLOCK - extra] ); + } + } + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java new file mode 100755 index 0000000..5016576 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java @@ -0,0 +1,96 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * @author Kamran + * + */ +public class TarUtils { + /** + * Determines the tar file size of the given folder/file path + * + * @param path + * @return + */ + public static long calculateTarSize(File path) { + return tarSize(path) + TarConstants.EOF_BLOCK; + } + + private static long tarSize(File dir) { + long size = 0; + + if (dir.isFile()) { + return entrySize(dir.length()); + } else { + File[] subFiles = dir.listFiles(); + + if (subFiles != null && subFiles.length > 0) { + for (File file : subFiles) { + if (file.isFile()) { + size += entrySize(file.length()); + } else { + size += tarSize(file); + } + } + } else { + // Empty folder header + return TarConstants.HEADER_BLOCK; + } + } + + return size; + } + + private static long entrySize(long fileSize) { + long size = 0; + size += TarConstants.HEADER_BLOCK; // Header + size += fileSize; // File size + + long extra = size % TarConstants.DATA_BLOCK; + + if (extra > 0) { + size += (TarConstants.DATA_BLOCK - extra); // pad + } + + return size; + } + + public static String trim(String s, char c) { + StringBuffer tmp = new StringBuffer(s); + for (int i = 0; i < tmp.length(); i++) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + for (int i = tmp.length() - 1; i >= 0; i--) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + return tmp.toString(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java new file mode 100644 index 0000000..58a1c5e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java @@ -0,0 +1,19 @@ +package org.kivy.android; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class GenericBroadcastReceiver extends BroadcastReceiver { + + GenericBroadcastReceiverCallback listener; + + public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) { + super(); + this.listener = listener; + } + + public void onReceive(Context context, Intent intent) { + this.listener.onReceive(context, intent); + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java new file mode 100644 index 0000000..1a87c98 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java @@ -0,0 +1,8 @@ +package org.kivy.android; + +import android.content.Intent; +import android.content.Context; + +public interface GenericBroadcastReceiverCallback { + void onReceive(Context context, Intent intent); +}; diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java new file mode 100644 index 0000000..b2f4a45 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java @@ -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, new File(getApplicationInfo().nativeLibraryDir)); + } + + public void recursiveDelete(File f) { + if (f.isDirectory()) { + for (File r : f.listFiles()) { + recursiveDelete(r); + } + } + f.delete(); + } + + /** + * Show an error using a toast. (Only makes sense from non-UI + * threads.) + */ + public void toastError(final String msg) { + + final Activity thisActivity = this; + + runOnUiThread(new Runnable () { + public void run() { + Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show(); + } + }); + + // Wait to show the error. + synchronized (this) { + try { + this.wait(1000); + } catch (InterruptedException e) { + } + } + } + + private class UnpackFilesTask extends AsyncTask { + @Override + protected String doInBackground(String... params) { + File app_root_file = new File(params[0]); + Log.v(TAG, "Ready to unpack"); + unpackData("private", app_root_file); + return null; + } + + @Override + protected void onPostExecute(String result) { + // Figure out the directory where the game is. If the game was + // given to us via an intent, then we use the scheme-specific + // part of that intent to determine the file to launch. We + // also use the android.txt file to determine the orientation. + // + // Otherwise, we use the public data, if we have it, or the + // private data if we do not. + mActivity.finishLoad(); + + // finishLoad called setContentView with the SDL view, which + // removed the loading screen. However, we still need it to + // show until the app is ready to render, so pop it back up + // on top of the SDL view. + mActivity.showLoadingScreen(); + + String app_root_dir = getAppRoot(); + if (getIntent() != null && getIntent().getAction() != null && + getIntent().getAction().equals("org.kivy.LAUNCH")) { + File path = new File(getIntent().getData().getSchemeSpecificPart()); + + Project p = Project.scanDirectory(path); + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", p.dir + "/main.py"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", p.dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", p.dir); + + if (p != null) { + if (p.landscape) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + // Let old apps know they started. + try { + FileWriter f = new FileWriter(new File(path, ".launch")); + f.write("started"); + f.close(); + } catch (IOException e) { + // pass + } + } else { + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir); + } + + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); + Log.v(TAG, "Setting env vars for start.c and Python to use"); + SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + SDLActivity.nativeSetEnv("PYTHONHOME", app_root_dir); + SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib"); + SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2"); + + try { + Log.v(TAG, "Access to our meta-data..."); + mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo( + mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); + if ( mActivity.mMetaData.getInt("wakelock") == 1 ) { + mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + mActivity.mWakeLock.acquire(); + } + if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) { + Log.v(TAG, "Surface will be transparent."); + getSurface().setZOrderOnTop(true); + getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + Log.i(TAG, "Surface will NOT be transparent"); + } + } catch (PackageManager.NameNotFoundException e) { + } + } + + @Override + protected void onPreExecute() { + } + + @Override + protected void onProgressUpdate(Void... values) { + } + } + + public void unpackData(final String resource, File target) { + + Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); + + // The version of data in memory and on disk. + String data_version = resourceManager.getString(resource + "_version"); + String disk_version = null; + + Log.v(TAG, "Data version is " + data_version); + + // If no version, no unpacking is necessary. + if (data_version == null) { + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String disk_version_fn = filesDir + "/" + resource + ".version"; + + try { + byte buf[] = new byte[64]; + InputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + disk_version = ""; + } + + // If the disk data is out of date, extract it and write the + // version file. + // if (! data_version.equals(disk_version)) { + if (! data_version.equals(disk_version)) { + Log.v(TAG, "Extracting " + resource + " assets."); + + recursiveDelete(target); + target.mkdirs(); + + AssetExtract ae = new AssetExtract(this); + if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { + toastError("Could not extract " + resource + " data."); + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(disk_version_fn); + os.write(data_version.getBytes()); + os.close(); + } catch (Exception e) { + Log.w("python", e); + } + } + } + + public static ViewGroup getLayout() { + return mLayout; + } + + public static SurfaceView getSurface() { + return mSurface; + } + + //---------------------------------------------------------------------------- + // Listener interface for onNewIntent + // + + public interface NewIntentListener { + void onNewIntent(Intent intent); + } + + private List newIntentListeners = null; + + public void registerNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + this.newIntentListeners = Collections.synchronizedList(new ArrayList()); + this.newIntentListeners.add(listener); + } + + public void unregisterNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + return; + this.newIntentListeners.remove(listener); + } + + @Override + protected void onNewIntent(Intent intent) { + if ( this.newIntentListeners == null ) + return; + this.onResume(); + synchronized ( this.newIntentListeners ) { + Iterator iterator = this.newIntentListeners.iterator(); + while ( iterator.hasNext() ) { + (iterator.next()).onNewIntent(intent); + } + } + } + + //---------------------------------------------------------------------------- + // Listener interface for onActivityResult + // + + public interface ActivityResultListener { + void onActivityResult(int requestCode, int resultCode, Intent data); + } + + private List activityResultListeners = null; + + public void registerActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + this.activityResultListeners = Collections.synchronizedList(new ArrayList()); + this.activityResultListeners.add(listener); + } + + public void unregisterActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + return; + this.activityResultListeners.remove(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if ( this.activityResultListeners == null ) + return; + this.onResume(); + synchronized ( this.activityResultListeners ) { + Iterator iterator = this.activityResultListeners.iterator(); + while ( iterator.hasNext() ) + (iterator.next()).onActivityResult(requestCode, resultCode, intent); + } + } + + public static void start_service(String serviceTitle, String serviceDescription, + String pythonServiceArgument) { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath(); + String filesDirectory = argument; + String app_root_dir = PythonActivity.mActivity.getAppRoot(); + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", app_root_dir); + serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo"); + serviceIntent.putExtra("pythonHome", app_root_dir); + serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib"); + serviceIntent.putExtra("serviceTitle", serviceTitle); + serviceIntent.putExtra("serviceDescription", serviceDescription); + serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument); + PythonActivity.mActivity.startService(serviceIntent); + } + + public static void stop_service() { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + PythonActivity.mActivity.stopService(serviceIntent); + } + + /** Loading screen implementation + * keepActive() is a method plugged in pollInputDevices in SDLActivity. + * Once it's called twice, the loading screen will be removed. + * The first call happen as soon as the window is created, but no image has been + * displayed first. My tests showed that we can wait one more. This might delay + * the real available of few hundred milliseconds. + * The real deal is to know if a rendering has already happen. The previous + * python-for-android and kivy was having something for that, but this new version + * is not compatible, and would require a new kivy version. + * In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called. + */ + public static ImageView mImageView = null; + int mLoadingCount = 2; + + @Override + public void keepActive() { + if (this.mLoadingCount > 0) { + this.mLoadingCount -= 1; + if (this.mLoadingCount == 0) { + this.removeLoadingScreen(); + } + } + } + + public void removeLoadingScreen() { + runOnUiThread(new Runnable() { + public void run() { + if (PythonActivity.mImageView != null && + PythonActivity.mImageView.getParent() != null) { + ((ViewGroup)PythonActivity.mImageView.getParent()).removeView( + PythonActivity.mImageView); + PythonActivity.mImageView = null; + } + } + }); + } + + + protected void showLoadingScreen() { + // load the bitmap + // 1. if the image is valid and we don't have layout yet, assign this bitmap + // as main view. + // 2. if we have a layout, just set it in the layout. + // 3. If we have an mImageView already, then do nothing because it will have + // already been made the content view or added to the layout. + + if (mImageView == null) { + int presplashId = this.resourceManager.getIdentifier("presplash", "drawable"); + InputStream is = this.getResources().openRawResource(presplashId); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(is); + } finally { + try { + is.close(); + } catch (IOException e) {}; + } + + mImageView = new ImageView(this); + mImageView.setImageBitmap(bitmap); + + /* + * Set the presplash loading screen background color + * https://developer.android.com/reference/android/graphics/Color.html + * Parse the color string, and return the corresponding color-int. + * If the string cannot be parsed, throws an IllegalArgumentException exception. + * Supported formats are: #RRGGBB #AARRGGBB or one of the following names: + * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', + * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia', + * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'. + */ + String backgroundColor = resourceManager.getString("presplash_color"); + if (backgroundColor != null) { + try { + mImageView.setBackgroundColor(Color.parseColor(backgroundColor)); + } catch (IllegalArgumentException e) {} + } + mImageView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + + } + + if (mLayout == null) { + setContentView(mImageView); + } else if (PythonActivity.mImageView.getParent() == null){ + mLayout.addView(mImageView); + } + + } + + @Override + protected void onPause() { + // fooabc + if ( this.mWakeLock != null && mWakeLock.isHeld()){ + this.mWakeLock.release(); + } + + Log.v(TAG, "onPause()"); + super.onPause(); + } + + @Override + protected void onResume() { + if ( this.mWakeLock != null){ + this.mWakeLock.acquire(); + } + Log.v(TAG, "onResume()"); + super.onResume(); + } + + + +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java new file mode 100644 index 0000000..db4884c --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java @@ -0,0 +1,138 @@ +package org.kivy.android; + +import android.app.Service; +import android.os.IBinder; +import android.os.Build; +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(); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + Notification notification = new Notification.Builder(context) + .setContentTitle(serviceTitle) + .setContentText(serviceDescription) + .setContentIntent(pIntent) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(android.R.drawable.ic_dialog_info) + .setOngoing(true) + .build(); + startForeground(1, notification); + } + + @Override + public void onDestroy() { + super.onDestroy(); + pythonThread = null; + if (autoRestartService && startIntent != null) { + Log.v("python service", "service restart requested"); + startService(startIntent); + } + Process.killProcess(Process.myPid()); + } + + @Override + public void run(){ + String app_root = getFilesDir().getAbsolutePath() + "/app"; + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir)); + this.mService = this; + nativeStart( + androidPrivate, androidArgument, + serviceEntrypoint, pythonName, + pythonHome, pythonPath, + pythonServiceArgument); + stopSelf(); + } + + // Native part + public static native void nativeStart( + String androidPrivate, String androidArgument, + String serviceEntrypoint, String pythonName, + String pythonHome, String pythonPath, + String pythonServiceArgument); +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java new file mode 100644 index 0000000..2dc888d --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java @@ -0,0 +1,68 @@ +package org.kivy.android; + +import java.io.File; +import java.util.ArrayList; +import java.util.regex.Pattern; + +import android.util.Log; + +public class PythonUtil { + private static final String TAG = "pythonutil"; + + protected static void addLibraryIfExists(ArrayList libsList, String pattern, File libsDir) { + // pattern should be the name of the lib file, without the + // preceding "lib" or suffix ".so", for instance "ssl.*" will + // match files of the form "libssl.*.so". + File [] files = libsDir.listFiles(); + + pattern = "lib" + pattern + "\\.so"; + Pattern p = Pattern.compile(pattern); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + String name = file.getName(); + Log.v(TAG, "Checking pattern " + pattern + " against " + name); + if (p.matcher(name).matches()) { + Log.v(TAG, "Pattern " + pattern + " matched file " + name); + libsList.add(name.substring(3, name.length() - 3)); + } + } + } + + protected static ArrayList getLibraries(File libsDir) { + ArrayList libsList = new ArrayList(); + libsList.add("python3.7m"); + libsList.add("python3.8"); + libsList.add("python3.9"); + libsList.add("main"); + return libsList; + } + + public static void loadLibraries(File filesDir, File libsDir) { + boolean foundPython = false; + + for (String lib : getLibraries(libsDir)) { + Log.v(TAG, "Loading library: " + lib); + try { + System.loadLibrary(lib); + if (lib.startsWith("python")) { + foundPython = true; + } + } catch(UnsatisfiedLinkError e) { + // If this is the last possible libpython + // load, and it has failed, give a more + // general error + Log.v(TAG, "Library loading error: " + e.getMessage()); + if (lib.startsWith("python3.9") && !foundPython) { + throw new RuntimeException("Could not load any libpythonXXX.so"); + } else if (lib.startsWith("python")) { + continue; + } else { + Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib); + throw e; + } + } + } + + Log.v(TAG, "Loaded everything!"); + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java new file mode 100644 index 0000000..9911356 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java @@ -0,0 +1,45 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonEvent { + private final Lock lock = new ReentrantLock(); + private final Condition cond = lock.newCondition(); + private boolean flag = false; + + public void set() { + lock.lock(); + try { + flag = true; + cond.signalAll(); + } finally { + lock.unlock(); + } + } + + public void wait_() throws InterruptedException { + lock.lock(); + try { + while (!flag) { + cond.await(); + } + } finally { + lock.unlock(); + } + } + + public void clear() { + lock.lock(); + try { + flag = false; + cond.signalAll(); + } finally { + lock.unlock(); + } + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java new file mode 100644 index 0000000..22f9d90 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java @@ -0,0 +1,19 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonLock { + private final Lock lock = new ReentrantLock(); + + public void acquire() { + lock.lock(); + } + + public void release() { + lock.unlock(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java new file mode 100644 index 0000000..9177b43 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java @@ -0,0 +1,99 @@ +package org.kivy.android.launcher; + +import java.io.UnsupportedEncodingException; +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +import android.util.Log; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + + +/** + * This represents a project we've scanned for. + */ +public class Project { + + public String dir = null; + String title = null; + String author = null; + Bitmap icon = null; + public boolean landscape = false; + + static String decode(String s) { + try { + return new String(s.getBytes("ISO-8859-1"), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return s; + } + } + + /** + * Scans directory for a android.txt file. If it finds one, + * and it looks valid enough, then it creates a new Project, + * and returns that. Otherwise, returns null. + */ + public static Project scanDirectory(File dir) { + + // We might have a link file. + if (dir.getAbsolutePath().endsWith(".link")) { + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + String directory = p.getProperty("directory", null); + + if (directory == null) { + return null; + } + + dir = new File(directory); + + } catch (Exception e) { + Log.i("Project", "Couldn't open link file " + dir, e); + } + } + + // Make sure we're dealing with a directory. + if (! dir.isDirectory()) { + return null; + } + + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + // Get the various properties. + String title = decode(p.getProperty("title", "Untitled")); + String author = decode(p.getProperty("author", "")); + boolean landscape = p.getProperty("orientation", "portrait").equals("landscape"); + + // Create the project object. + Project rv = new Project(); + rv.title = title; + rv.author = author; + rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath()); + rv.landscape = landscape; + rv.dir = dir.getAbsolutePath(); + + return rv; + + } catch (Exception e) { + Log.i("Project", "Couldn't open android.txt", e); + } + + return null; + + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java new file mode 100644 index 0000000..f66debf --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java @@ -0,0 +1,44 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.view.Gravity; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.ImageView; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import org.renpy.android.ResourceManager; + +public class ProjectAdapter extends ArrayAdapter { + + private Activity mContext; + private ResourceManager resourceManager; + + public ProjectAdapter(Activity context) { + super(context, 0); + + mContext = context; + resourceManager = new ResourceManager(context); + } + + public View getView(int position, View convertView, ViewGroup parent) { + Project p = getItem(position); + + View v = resourceManager.inflateView("chooser_item"); + TextView title = (TextView) resourceManager.getViewById(v, "title"); + TextView author = (TextView) resourceManager.getViewById(v, "author"); + ImageView icon = (ImageView) resourceManager.getViewById(v, "icon"); + + title.setText(p.title); + author.setText(p.author); + icon.setImageBitmap(p.icon); + + return v; + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java new file mode 100644 index 0000000..17eec32 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java @@ -0,0 +1,94 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.os.Bundle; + +import android.content.Intent; +import android.content.res.Resources; +import android.util.Log; +import android.view.View; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView; +import android.os.Environment; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import android.net.Uri; + +import org.renpy.android.ResourceManager; + +public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener { + + ResourceManager resourceManager; + + String urlScheme; + + @Override + public void onStart() + { + super.onStart(); + + resourceManager = new ResourceManager(this); + + urlScheme = resourceManager.getString("urlScheme"); + + // Set the window title. + setTitle(resourceManager.getString("appName")); + + // Scan the sdcard for files, and sort them. + File dir = new File(Environment.getExternalStorageDirectory(), urlScheme); + + File entries[] = dir.listFiles(); + + if (entries == null) { + entries = new File[0]; + } + + Arrays.sort(entries); + + // Create a ProjectAdapter and fill it with projects. + ProjectAdapter projectAdapter = new ProjectAdapter(this); + + // Populate it with the properties files. + for (File d : entries) { + Project p = Project.scanDirectory(d); + if (p != null) { + projectAdapter.add(p); + } + } + + if (projectAdapter.getCount() != 0) { + + View v = resourceManager.inflateView("project_chooser"); + ListView l = (ListView) resourceManager.getViewById(v, "projectList"); + + l.setAdapter(projectAdapter); + l.setOnItemClickListener(this); + + setContentView(v); + + } else { + + View v = resourceManager.inflateView("project_empty"); + TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText"); + + emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit."); + + setContentView(v); + } + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + Project p = (Project) parent.getItemAtPosition(position); + + Intent intent = new Intent( + "org.kivy.LAUNCH", + Uri.fromParts(urlScheme, p.dir, "")); + + intent.setClassName(getPackageName(), "org.kivy.android.PythonActivity"); + this.startActivity(intent); + this.finish(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java new file mode 100644 index 0000000..e1dc084 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java @@ -0,0 +1,1579 @@ +package org.libsdl.app; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.lang.reflect.Method; + +import android.app.*; +import android.content.*; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.os.*; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.media.*; +import android.hardware.*; + +/** + SDL Activity +*/ +public class SDLActivity extends Activity { + private static final String TAG = "SDL"; + + // Keep track of the paused state + public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mExitCalledFromJava; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static View mTextEdit; + protected static ViewGroup mLayout; + protected static SDLJoystickHandler mJoystickHandler; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + // Audio + protected static AudioTrack mAudioTrack; + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } + } + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mSingleton = null; + mSurface = null; + mTextEdit = null; + mLayout = null; + mJoystickHandler = null; + mSDLThread = null; + mAudioTrack = null; + mExitCalledFromJava = false; + mBrokenLibraries = false; + mIsPaused = false; + mIsSurfaceReady = false; + mHasFocus = true; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v("SDL", "Device: " + android.os.Build.DEVICE); + Log.v("SDL", "Model: " + android.os.Build.MODEL); + Log.v("SDL", "onCreate():" + mSingleton); + super.onCreate(savedInstanceState); + + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + } + + // We don't do this in onCreate because we unpack and load the app data on a thread + // and we can't run setup tasks until that thread completes. + protected void finishLoad() { + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up the surface + mSurface = new SDLSurface(getApplication()); + + if(Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } + else { + mJoystickHandler = new SDLJoystickHandler(); + } + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mSurface); + + setContentView(mLayout); + } + + // Events + @Override + protected void onPause() { + Log.v("SDL", "onPause()"); + super.onPause(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handlePause(); + } + + @Override + protected void onResume() { + Log.v("SDL", "onResume()"); + super.onResume(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleResume(); + } + + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.mHasFocus = hasFocus; + if (hasFocus) { + SDLActivity.handleResume(); + } + } + + @Override + public void onLowMemory() { + Log.v("SDL", "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v("SDL", "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + // Send a quit message to the application + SDLActivity.mExitCalledFromJava = true; + SDLActivity.nativeQuit(); + + // Now wait for the SDL thread to quit + if (SDLActivity.mSDLThread != null) { + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v("SDL", "Problem stopping thread: " + e); + } + SDLActivity.mSDLThread = null; + + //Log.v("SDL", "Finished waiting for SDL thread"); + } + + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + + // Completely closes application. + System.exit(0); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + + int keyCode = event.getKeyCode(); + // Ignore certain special keys so they're handled by Android + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_CAMERA || + keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ + keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed + * is the first to be called, mIsSurfaceReady should still be set + * to 'true' during the call to onPause (in a usual scenario). + */ + public static void handlePause() { + if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { + SDLActivity.mIsPaused = true; + SDLActivity.nativePause(); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + } + + /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. + * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume + * every time we get one of those events, only if it comes after surfaceDestroyed + */ + public static void handleResume() { + if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { + SDLActivity.mIsPaused = false; + SDLActivity.nativeResume(); + mSurface.handleResume(); + } + } + + /* The native thread has finished */ + public static void handleNativeExit() { + SDLActivity.mSDLThread = null; + mSingleton.finish(); + } + + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_UNUSED = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + protected boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + protected static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Context context = getContext(); + if (context == null) { + Log.e(TAG, "error handling message, getContext() returned null"); + return; + } + switch (msg.arg1) { + case COMMAND_CHANGE_TITLE: + if (context instanceof Activity) { + ((Activity) context).setTitle((String)msg.obj); + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + mTextEdit.setVisibility(View.GONE); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + break; + } + default: + if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { + Log.e(TAG, "error handling message, command is " + msg.arg1); + } + } + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + return commandHandler.sendMessage(msg); + } + + // C functions we call + public static native int nativeInit(Object arguments); + public static native void nativeLowMemory(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void onNativeResize(int x, int y, int format, float rate); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + public static native void nativeSetEnv(String j_name, String j_value); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native void nativeFlipBuffers(); + public static native int nativeAddJoystick(int device_id, String name, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); + + /** + * This method is called by SDL using JNI. + */ + public static void flipBuffers() { + SDLActivity.nativeFlipBuffers(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + // Called from SDLMain() thread and can't directly affect the view + return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return mSingleton; + } + + /** + * This method is called by SDL using JNI. + * @return result of getSystemService(name) but executed on UI thread. + */ + public Object getSystemServiceFromUiThread(final String name) { + final Object lock = new Object(); + final Object[] results = new Object[2]; // array for writable variables + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + results[0] = getSystemService(name); + results[1] = Boolean.TRUE; + lock.notify(); + } + } + }); + if (results[1] == null) { + try { + lock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + return results[0]; + } + + static class ShowTextInputTask implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputTask(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + @Override + public void run() { + AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( + w, h + HEIGHT_PADDING, x, y); + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + } + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return SDLActivity.mSurface.getNativeSurface(); + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e("SDL", "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioQuit() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack = null; + } + } + + // Input + + /** + * This method is called by SDL using JNI. + * @return an array which may be empty but is never null. + */ + public static int[] inputGetInputDeviceIds(int sources) { + int[] ids = InputDevice.getDeviceIds(); + int[] filtered = new int[ids.length]; + int used = 0; + for (int i = 0; i < ids.length; ++i) { + InputDevice device = InputDevice.getDevice(ids[i]); + if ((device != null) && ((device.getSources() & sources) != 0)) { + filtered[used++] = device.getId(); + } + } + return Arrays.copyOf(filtered, used); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + if (SDLActivity.mSDLThread != null) { + mJoystickHandler.pollInputDevices(); + SDLActivity.mSingleton.keepActive(); + } + } + + /** + * Trick needed for loading screen + */ + public void keepActive() { + } + + // APK extension files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + */ + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION")); + Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION")); + + try { + // To avoid direct dependency on Google APK extension library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, this, mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + ex.printStackTrace(); + fileStream = null; + } + + if (fileStream == null) { + throw new IOException(); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray