From 4f720ece5881521b021757d62ec6c268fa7b0910 Mon Sep 17 00:00:00 2001 From: akinwale Date: Thu, 21 Dec 2017 08:24:31 +0100 Subject: [PATCH] Switch build tool to Gradle (#15) --- .travis.yml | 6 + buildozer.spec.sample | 2 +- buildozer.spec.travis | 2 +- p4a/LICENSE | 2 +- p4a/README.rst | 102 ++ p4a/doc/source/buildoptions.rst | 2 +- p4a/doc/source/distutils.rst | 2 +- p4a/doc/source/old_toolchain/index.rst | 4 + p4a/doc/source/quickstart.rst | 28 +- p4a/doc/source/services.rst | 12 +- p4a/doc/source/troubleshooting.rst | 18 + p4a/pythonforandroid/bootstrap.py | 13 +- .../bootstraps/lbry/__init__.py | 121 +- .../bootstraps/lbry/build/.gitignore | 14 + .../bootstraps/lbry/build/ant.properties | 4 + .../bootstraps/lbry/build/build.py | 209 ++- .../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 + .../lbry/build/res/values/colors.xml | 9 - .../lbry/build/res/values/strings.xml | 9 +- .../lbry/build/src/main/assets/.gitkeep | 0 .../lbry/build/src/main/java/.gitkeep | 0 .../java}/org/kamranzafar/jtar/Octal.java | 0 .../org/kamranzafar/jtar/TarConstants.java | 0 .../java}/org/kamranzafar/jtar/TarEntry.java | 0 .../java}/org/kamranzafar/jtar/TarHeader.java | 0 .../org/kamranzafar/jtar/TarInputStream.java | 0 .../org/kamranzafar/jtar/TarOutputStream.java | 0 .../java}/org/kamranzafar/jtar/TarUtils.java | 0 .../android/GenericBroadcastReceiver.java | 0 .../GenericBroadcastReceiverCallback.java | 0 .../org/kivy/android/PythonActivity.java | 0 .../java}/org/kivy/android/PythonService.java | 0 .../java}/org/kivy/android/PythonUtil.java | 0 .../kivy/android/concurrency/PythonEvent.java | 0 .../kivy/android/concurrency/PythonLock.java | 0 .../org/kivy/android/launcher/Project.java | 0 .../kivy/android/launcher/ProjectAdapter.java | 0 .../kivy/android/launcher/ProjectChooser.java | 0 .../java}/org/libsdl/app/SDLActivity.java | 0 .../java}/org/renpy/android/AssetExtract.java | 0 .../java}/org/renpy/android/Hardware.java | 0 .../org/renpy/android/PythonActivity.java | 0 .../org/renpy/android/PythonService.java | 0 .../org/renpy/android/ResourceManager.java | 0 .../lbry/build/src/main/jniLibs/.gitkeep | 0 .../lbry/build/src/main/libs/.gitkeep | 0 .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2683 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1698 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3872 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 6874 bytes .../lbry/build/src/main/res/drawable/.gitkeep | 0 .../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 | 18 +- .../lbry/build/templates/Service.tmpl.java | 27 +- .../build/templates/app.build.tmpl.gradle | 59 - .../lbry/build/templates/build.tmpl.gradle | 70 + .../build/templates/custom_rules.tmpl.xml | 4 +- .../lbry/build/templates/kivy-icon.png | Bin 16525 -> 3229 bytes .../lbry/build/templates/kivy-presplash.jpg | Bin 18251 -> 11584 bytes .../lbry/build/templates/strings.tmpl.xml | 4 +- .../bootstraps/pygame/__init__.py | 2 - .../src/org/renpy/android/PythonService.java | 28 +- .../src/org/renpy/android/SDLSurfaceView.java | 1 + .../bootstraps/sdl2/__init__.py | 129 +- .../bootstraps/sdl2/build/.gitignore | 14 + .../bootstraps/sdl2/build/ant.properties | 4 + .../bootstraps/sdl2/build/build.py | 203 ++- .../build/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51017 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../bootstraps/sdl2/build/gradlew | 164 ++ .../bootstraps/sdl2/build/gradlew.bat | 90 + .../bootstraps/sdl2/build/jni/src/start.c | 20 +- .../sdl2/build/src/main/assets/.gitkeep | 0 .../sdl2/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 | 480 +++++ .../java/org/kivy/android/PythonService.java | 153 ++ .../java/org/kivy/android/PythonUtil.java | 97 + .../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 | 115 ++ .../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 | 54 + .../sdl2/build/src/main/jniLibs/.gitkeep | 0 .../sdl2/build/src/main/libs/.gitkeep | 0 .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2683 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1698 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3872 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 6874 bytes .../sdl2/build/src/main/res/drawable/.gitkeep | 0 .../src/main/res/layout/chooser_item.xml | 39 + .../sdl2/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 | 4 +- .../sdl2/build/templates/Service.tmpl.java | 27 +- .../sdl2/build/templates/build.tmpl.gradle | 70 + .../build/templates/custom_rules.tmpl.xml | 4 +- .../sdl2/build/templates/kivy-icon.png | Bin 16525 -> 3229 bytes .../sdl2/build/templates/kivy-presplash.jpg | Bin 18251 -> 11584 bytes .../sdl2/build/templates/strings.tmpl.xml | 2 +- .../bootstraps/service_only/__init__.py | 1 - .../bootstraps/service_only/build/build.py | 7 +- .../src/org/kivy/android/PythonService.java | 6 +- .../build/templates/Service.tmpl.java | 1 + .../bootstraps/webview/__init__.py | 1 - .../bootstraps/webview/build/build.py | 7 +- .../src/org/kivy/android/PythonActivity.java | 1 + .../src/org/kivy/android/PythonService.java | 27 +- .../webview/build/templates/Service.tmpl.java | 27 +- .../webview/build/webview_includes/_load.html | 2 +- .../build/webview_includes/_loading_style.css | 78 + p4a/pythonforandroid/toolchain.py | 135 +- p4a/setup.py | 25 +- p4a/testapps/setup_pygame.py | 2 +- p4a/testapps/setup_testapp_flask.py | 1 + .../setup_testapp_python2_sqlite_openssl.py | 29 + .../testapp_sqlite_openssl/colours.png | Bin 0 -> 191254 bytes p4a/testapps/testapp_sqlite_openssl/main.py | 222 +++ .../java/io/lbry/lbrynet/LbrynetService.java | 11 +- .../lbrynet/LbrynetTestRunnerService.java | 11 +- 141 files changed, 6362 insertions(+), 500 deletions(-) create mode 100644 p4a/README.rst create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/.gitignore 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 delete mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.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 rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/Octal.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarConstants.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarEntry.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarHeader.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarInputStream.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarOutputStream.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kamranzafar/jtar/TarUtils.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/GenericBroadcastReceiver.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/GenericBroadcastReceiverCallback.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/PythonActivity.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/PythonService.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/PythonUtil.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/concurrency/PythonEvent.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/concurrency/PythonLock.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/launcher/Project.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/launcher/ProjectAdapter.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/kivy/android/launcher/ProjectChooser.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/libsdl/app/SDLActivity.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/renpy/android/AssetExtract.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/renpy/android/Hardware.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/renpy/android/PythonActivity.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/renpy/android/PythonService.java (100%) rename p4a/pythonforandroid/bootstraps/lbry/build/src/{ => main/java}/org/renpy/android/ResourceManager.java (100%) 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 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep 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 delete mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/gradlew create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java create mode 100755 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/AssetExtract.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/Hardware.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonActivity.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonService.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/ResourceManager.java create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/jniLibs/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/libs/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable/.gitkeep create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml create mode 100644 p4a/pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.gradle create mode 100644 p4a/pythonforandroid/bootstraps/webview/build/webview_includes/_loading_style.css create mode 100644 p4a/testapps/setup_testapp_python2_sqlite_openssl.py create mode 100644 p4a/testapps/testapp_sqlite_openssl/colours.png create mode 100644 p4a/testapps/testapp_sqlite_openssl/main.py diff --git a/.travis.yml b/.travis.yml index 3e0a60e..f82f68c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,17 @@ install: - wget 'https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip' -P ~/.buildozer/android/platform/ - wget 'https://dl.google.com/android/android-sdk_r21-linux.tgz' -P ~/.buildozer/android/platform/ - wget 'https://dl.google.com/android/repository/android-21_r02.zip' -P ~/.buildozer/android/platform/ + - wget 'https://dl.google.com/android/repository/build-tools_r26.0.1-linux.zip' -P ~/.buildozer/android/platform/ - unzip -qq ~/.buildozer/android/platform/android-ndk-r13b-linux-x86_64.zip -d ~/.buildozer/android/platform/ - tar -xf ~/.buildozer/android/platform/android-sdk_r21-linux.tgz -C ~/.buildozer/android/platform/ - mv ~/.buildozer/android/platform/android-sdk-linux ~/.buildozer/android/platform/android-sdk-21 - unzip -qq ~/.buildozer/android/platform/android-21_r02.zip -d ~/.buildozer/android/platform/android-sdk-21/platforms - mv ~/.buildozer/android/platform/android-sdk-21/platforms/android-5.0.1 ~/.buildozer/android/platform/android-sdk-21/platforms/android-21 + - mkdir -p ~/.buildozer/android/platform/android-sdk-21/build-tools + - unzip -qq ~/.buildozer/android/platform/build-tools_r26.0.1-linux.zip -d ~/.buildozer/android/platform/android-sdk-21/build-tools + - mv ~/.buildozer/android/platform/android-sdk-21/build-tools/android-8.0.0 ~/.buildozer/android/platform/android-sdk-21/build-tools/26.0.1 + - mkdir -p ~/.buildozer/android/platform/android-sdk-21/licenses + - echo $'\nd56f5187479451eabf01fb78af6dfcb131a6481e' > ~/.buildozer/android/platform/android-sdk-21/licenses/android-sdk-license script: - buildozer android debug | grep -v 'working:' --line-buffered - cp bin/*.apk /dev/null \ No newline at end of file diff --git a/buildozer.spec.sample b/buildozer.spec.sample index a0f7a48..4075a59 100644 --- a/buildozer.spec.sample +++ b/buildozer.spec.sample @@ -145,7 +145,7 @@ android.add_src = ./src/main/java # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) -#android.gradle_dependencies = +android.gradle_dependencies = com.android.support:appcompat-v7:21.0.3 # (str) python-for-android branch to use, defaults to master #p4a.branch = stable diff --git a/buildozer.spec.travis b/buildozer.spec.travis index a0f7a48..4075a59 100644 --- a/buildozer.spec.travis +++ b/buildozer.spec.travis @@ -145,7 +145,7 @@ android.add_src = ./src/main/java # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) -#android.gradle_dependencies = +android.gradle_dependencies = com.android.support:appcompat-v7:21.0.3 # (str) python-for-android branch to use, defaults to master #p4a.branch = stable diff --git a/p4a/LICENSE b/p4a/LICENSE index b336a3a..d5d6b13 100644 --- a/p4a/LICENSE +++ b/p4a/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2015 Kivy Team and other contributors +Copyright (c) 2010-2017 Kivy Team and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/p4a/README.rst b/p4a/README.rst new file mode 100644 index 0000000..63505a4 --- /dev/null +++ b/p4a/README.rst @@ -0,0 +1,102 @@ +python-for-android +================== + +python-for-android is a packager for Python apps on Android. You can +create your own Python distribution including the modules and +dependencies you want, and bundle it in an APK along with your own code. + +Features include: + +- Support for building with both Python 2 and Python 3. +- Different app backends including Kivy, PySDL2, and a WebView with + Python webserver. +- Automatic support for most pure Python modules, and built in support + for many others, including popular dependencies such as numpy and + sqlalchemy. +- Multiple architecture targets, for APKs optimised on any given + device. + +For documentation and support, see: + +- Website: http://python-for-android.readthedocs.io +- Mailing list: https://groups.google.com/forum/#!forum/kivy-users or + https://groups.google.com/forum/#!forum/python-android. + +In 2015 these tools were rewritten to provide a new, easier to use and +extend interface. If you are looking for the old toolchain with +distribute.sh and build.py, it is still available at +https://github.com/kivy/python-for-android/tree/old\_toolchain, and +issues and PRs relating to this branch are still accepted. However, the +new toolchain contains all the same functionality via the built in +pygame bootstrap. + +Documentation +============= + +Follow the `quickstart +instructions `__ +to install and begin creating APKs. + +Quick instructions to start would be:: + + pip install python-for-android + +or to test the master branch:: + + pip install git+https://github.com/kivy/python-for-android.git + +The executable is called ``python-for-android`` or ``p4a`` (both are +equivalent). To test that the installation worked, try:: + + python-for-android recipes + +This should return a list of recipes available to be built. + +To build any distributions, you need to set up the Android SDK and NDK +as described in the documentation linked above. + +If you did this, to build an APK with SDL2 you can try e.g.:: + + p4a apk --requirements=kivy --private /home/asandy/devel/planewave_frozen/ --package=net.inclem.planewavessdl2 --name="planewavessdl2" --version=0.5 --bootstrap=sdl2 + +For full instructions and parameter options, see `the +documentation `__. + +Support +======= + +If you need assistance, you can ask for help on our mailing list: + +- User Group: https://groups.google.com/group/kivy-users +- Email: kivy-users@googlegroups.com + +We also have an IRC channel: + +- Server: irc.freenode.net +- Port: 6667, 6697 (SSL only) +- Channel: #kivy + +Contributing +============ + +We love pull requests and discussing novel ideas. Check out our +`contribution guide `__ and feel +free to improve python-for-android. + +The following mailing list and IRC channel are used exclusively for +discussions about developing the Kivy framework and its sister projects: + +- Dev Group: https://groups.google.com/group/kivy-dev +- Email: kivy-dev@googlegroups.com + +IRC channel: + +- Server: irc.freenode.net +- Port: 6667, 6697 (SSL only) +- Channel: #kivy or #kivy-dev + +License +======= + +python-for-android is released under the terms of the MIT License. +Please refer to the LICENSE file. diff --git a/p4a/doc/source/buildoptions.rst b/p4a/doc/source/buildoptions.rst index a8ff5f5..bb95d39 100644 --- a/p4a/doc/source/buildoptions.rst +++ b/p4a/doc/source/buildoptions.rst @@ -95,7 +95,7 @@ options (this list may not be exhaustive): ``android:screenOrientation`` in the `Android documentation `__. - ``--icon``: A path to the png file to use as the application icon. -- ``-- permission``: A permission name for the app, +- ``--permission``: A permission name for the app, e.g. ``--permission VIBRATE``. For multiple permissions, add multiple ``--permission`` arguments. - ``--meta-data``: Custom key=value pairs to add in the application metadata. diff --git a/p4a/doc/source/distutils.rst b/p4a/doc/source/distutils.rst index 8092654..aaa15f7 100644 --- a/p4a/doc/source/distutils.rst +++ b/p4a/doc/source/distutils.rst @@ -15,7 +15,7 @@ example will include all .py and .png files in the ``testapp`` folder:: from distutils.core import setup - from setup + from setuptools import find_packages setup( name='testapp_setup', diff --git a/p4a/doc/source/old_toolchain/index.rst b/p4a/doc/source/old_toolchain/index.rst index cc7faa1..1b873be 100644 --- a/p4a/doc/source/old_toolchain/index.rst +++ b/p4a/doc/source/old_toolchain/index.rst @@ -7,6 +7,10 @@ using distribute.sh and build.py. This it entirely superseded by the new toolchain, you do not need to read it unless using this old method. +.. warning:: The old toolchain is deprecated and no longer + supported. You should instead use the :doc:`current version + <../quickstart>`. + Python for android is a project to create your own Python distribution including the modules you want, and create an apk including python, libs, and your application. diff --git a/p4a/doc/source/quickstart.rst b/p4a/doc/source/quickstart.rst index b67ae56..129a1e1 100644 --- a/p4a/doc/source/quickstart.rst +++ b/p4a/doc/source/quickstart.rst @@ -84,15 +84,37 @@ Installing Android SDK You need to download and unpack the Android SDK and NDK to a directory (let's say $HOME/Documents/): -- `Android SDK `_ +- `Android SDK `_ - `Android NDK `_ +For the Android SDK, you can download 'just the command line +tools'. When you have extracted these you'll see only a directory +named ``tools``, and you will need to run extra commands to install +the SDK packages needed. + +For Android NDK, note that modern releases will only work on a 64-bit +operating system. If you are using a 32-bit distribution (or hardware), +the latest useable NDK version is r10e, which can be downloaded here: + +- `Legacy 32-bit Linux NDK r10e `_ + +First, install a platform to target (you can also replace ``19`` with +a different platform number, this will be used again later):: + + $SDK_DIR/tools/bin/sdkmanager "platforms;android-19" + +Second, install the build-tools. You can use +``$SDK_DIR/tools/bin/sdkmanager --list`` to see all the +possibilities, but 26.0.2 is the latest version at the time of writing:: + + $SDK_DIR/tools/bin/sdkmanager "build-tools;26.0.2" + Then, you can edit your ``~/.bashrc`` or other favorite shell to include new environment variables necessary for building on android:: # Adjust the paths! export ANDROIDSDK="$HOME/Documents/android-sdk-21" export ANDROIDNDK="$HOME/Documents/android-ndk-r10e" - export ANDROIDAPI="14" # Minimum API version your application require + export ANDROIDAPI="19" # Minimum API version your application require export ANDROIDNDKVER="r10e" # Version of the NDK you installed You have the possibility to configure on any command the PATH to the SDK, NDK and Android API using: @@ -100,7 +122,7 @@ You have the possibility to configure on any command the PATH to the SDK, NDK an - :code:`--sdk_dir PATH` as an equivalent of `$ANDROIDSDK` - :code:`--ndk_dir PATH` as an equivalent of `$ANDROIDNDK` - :code:`--android_api VERSION` as an equivalent of `$ANDROIDAPI` -- :code:`--ndk_ver PATH` as an equivalent of `$ANDROIDNDKVER` +- :code:`--ndk_version PATH` as an equivalent of `$ANDROIDNDKVER` Usage diff --git a/p4a/doc/source/services.rst b/p4a/doc/source/services.rst index d2ea1ee..a250c7b 100644 --- a/p4a/doc/source/services.rst +++ b/p4a/doc/source/services.rst @@ -80,8 +80,10 @@ Services support a range of options and interactions not yet documented here but all accessible via calling other methods of the ``service`` reference. -.. note:: The app root directory for Python imports will be in the app -root folder even if the service file is in a subfolder. To import from -your service folder you must use e.g. ``import service.module`` -instead of ``import module``, if the service file is in the -``service/`` folder. +.. note:: + + The app root directory for Python imports will be in the app + root folder even if the service file is in a subfolder. To import from + your service folder you must use e.g. ``import service.module`` + instead of ``import module``, if the service file is in the + ``service/`` folder. diff --git a/p4a/doc/source/troubleshooting.rst b/p4a/doc/source/troubleshooting.rst index dcd4788..975b29e 100644 --- a/p4a/doc/source/troubleshooting.rst +++ b/p4a/doc/source/troubleshooting.rst @@ -160,3 +160,21 @@ This error appears in the logcat log if you try to access ``org.renpy.android.PythonActivity`` from within the new toolchain. To fix it, change your code to reference ``org.kivy.android.PythonActivity`` instead. + +websocket-client: if you see errors relating to 'SSL not available' +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ensure you have the package backports.ssl-match-hostname in the buildozer requirements, since Kivy targets python 2.7.x + +You may also need sslopt={"cert_reqs": ssl.CERT_NONE} as a parameter to ws.run_forever() if you get an error relating to host verification + +Requested API target 19 is not available, install it with the SDK android tool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This means that your SDK is missing the required platform tools. You +need to install the ``platforms;android-19`` package in your SDK, +using the ``android`` or ``sdkmanager`` tools (depending on SDK +version). + +If using buildozer this should be done automatically, but as a +workaround you can run these from +``~/.buildozer/android/platform/android-sdk-20/tools/android``. diff --git a/p4a/pythonforandroid/bootstrap.py b/p4a/pythonforandroid/bootstrap.py index c1b6633..f7aa6c9 100644 --- a/p4a/pythonforandroid/bootstrap.py +++ b/p4a/pythonforandroid/bootstrap.py @@ -6,7 +6,7 @@ import json import importlib from pythonforandroid.logger import (warning, shprint, info, logger, - debug) + debug, error) from pythonforandroid.util import (current_directory, ensure_dir, temp_directory, which) from pythonforandroid.recipe import Recipe @@ -178,8 +178,6 @@ class Bootstrap(object): This is the only way you should access a bootstrap class, as it sets the bootstrap directory correctly. ''' - # AND: This method will need to check user dirs, and access - # bootstraps in a slightly different way if name is None: return None if not hasattr(cls, 'bootstraps'): @@ -195,20 +193,21 @@ class Bootstrap(object): bootstrap.ctx = ctx return bootstrap - def distribute_libs(self, arch, src_dirs, wildcard='*'): + def distribute_libs(self, arch, src_dirs, wildcard='*', dest_dir="libs"): '''Copy existing arch libs from build dirs to current dist dir.''' info('Copying libs') - tgt_dir = join('libs', arch.arch) + tgt_dir = join(dest_dir, arch.arch) ensure_dir(tgt_dir) for src_dir in src_dirs: for lib in glob.glob(join(src_dir, wildcard)): shprint(sh.cp, '-a', lib, tgt_dir) - def distribute_javaclasses(self, javaclass_dir): + def distribute_javaclasses(self, javaclass_dir, dest_dir="src"): '''Copy existing javaclasses from build dir to current dist dir.''' info('Copying java files') + ensure_dir(dest_dir) for filename in glob.glob(javaclass_dir): - shprint(sh.cp, '-a', filename, 'src') + shprint(sh.cp, '-a', filename, dest_dir) def distribute_aars(self, arch): '''Process existing .aar bundles and copy to current dist dir.''' diff --git a/p4a/pythonforandroid/bootstraps/lbry/__init__.py b/p4a/pythonforandroid/bootstraps/lbry/__init__.py index c16640f..0cafc21 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/__init__.py +++ b/p4a/pythonforandroid/bootstraps/lbry/__init__.py @@ -1,67 +1,81 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir from os.path import join, exists, curdir, abspath from os import walk import glob import sh + +EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx") + + class LbryBootstrap(Bootstrap): name = 'lbry' recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')] def run_distribute(self): - info_main('# Creating Android project from build and {} bootstrap'.format( - self.name)) + info_main("# Creating Android project ({})".format(self.name)) - info('This currently just copies the build stuff straight from the build dir.') - shprint(sh.rm, '-rf', self.dist_dir) - shprint(sh.cp, '-r', self.build_dir, self.dist_dir) + arch = self.ctx.archs[0] + python_install_dir = self.ctx.get_python_install_dir() + from_crystax = self.ctx.python_recipe.from_crystax + crystax_python_dir = join("crystax_python", "crystax_python") + + if len(self.ctx.archs) > 1: + raise ValueError("LBRY/gradle support only one arch") + + info("Copying LBRY/gradle build for {}".format(arch)) + shprint(sh.rm, "-rf", self.dist_dir) + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environemnt variable (ANDROID_HOME) + # or the local.properties if exists with current_directory(self.dist_dir): with open('local.properties', 'w') as fileh: fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) - arch = self.ctx.archs[0] - if len(self.ctx.archs) > 1: - raise ValueError('built for more than one arch, but bootstrap cannot handle that yet') - info('Bootstrap running with arch {}'.format(arch)) - with current_directory(self.dist_dir): - info('Copying python distribution') + info("Copying Python distribution") - if not exists('private') and not self.ctx.python_recipe.from_crystax: - shprint(sh.mkdir, 'private') - if not exists('crystax_python') and self.ctx.python_recipe.from_crystax: - shprint(sh.mkdir, 'crystax_python') - shprint(sh.mkdir, 'crystax_python/crystax_python') - if not exists('assets'): - shprint(sh.mkdir, 'assets') + if not exists("private") and not from_crystax: + ensure_dir("private") + if not exists("crystax_python") and from_crystax: + ensure_dir(crystax_python_dir) hostpython = sh.Command(self.ctx.hostpython) - if not self.ctx.python_recipe.from_crystax: + if not from_crystax: try: shprint(hostpython, '-OO', '-m', 'compileall', - self.ctx.get_python_install_dir(), + python_install_dir, _tail=10, _filterout="^Listing") except sh.ErrorReturnCode: pass if not exists('python-install'): - shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install') + shprint( + sh.cp, '-a', python_install_dir, './python-install') self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) - self.distribute_aars(arch) - self.distribute_javaclasses(self.ctx.javaclass_dir) + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) - if not self.ctx.python_recipe.from_crystax: - info('Filling private directory') - if not exists(join('private', 'lib')): - info('private/lib does not exist, making') - shprint(sh.cp, '-a', join('python-install', 'lib'), 'private') - shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7')) + if not from_crystax: + info("Filling private directory") + if not exists(join("private", "lib")): + info("private/lib does not exist, making") + shprint(sh.cp, "-a", + join("python-install", "lib"), "private") + shprint(sh.mkdir, "-p", + join("private", "include", "python2.7")) - # AND: Copylibs stuff should go here - if exists(join('libs', arch.arch, 'libpymodules.so')): - shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/') - shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/')) + libpymodules_fn = join("libs", arch.arch, "libpymodules.so") + if exists(libpymodules_fn): + shprint(sh.mv, libpymodules_fn, 'private/') + shprint(sh.cp, + join('python-install', 'include', + 'python2.7', 'pyconfig.h'), + join('private', 'include', 'python2.7/')) info('Removing some unwanted files') shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so')) @@ -70,54 +84,53 @@ class LbryBootstrap(Bootstrap): libdir = join(self.dist_dir, 'private', 'lib', 'python2.7') site_packages_dir = join(libdir, 'site-packages') with current_directory(libdir): - # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.'))) removes = [] - for dirname, something, filens in walk('.'): - for filename in filens: - for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'): + for dirname, root, filenames in walk("."): + for filename in filenames: + for suffix in EXCLUDE_EXTS: if filename.endswith(suffix): removes.append(filename) shprint(sh.rm, '-f', *removes) info('Deleting some other stuff not used on android') # To quote the original distribute.sh, 'well...' - # shprint(sh.rm, '-rf', 'ctypes') shprint(sh.rm, '-rf', 'lib2to3') shprint(sh.rm, '-rf', 'idlelib') for filename in glob.glob('config/libpython*.a'): shprint(sh.rm, '-f', filename) shprint(sh.rm, '-rf', 'config/python.o') - # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so') - # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so') else: # Python *is* loaded from crystax ndk_dir = self.ctx.ndk_dir py_recipe = self.ctx.python_recipe - python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version, - 'libs', arch.arch) - - shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python') - shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python') - shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages') + python_dir = join(ndk_dir, 'sources', 'python', + py_recipe.version, 'libs', arch.arch) + shprint(sh.cp, '-r', join(python_dir, + 'stdlib.zip'), crystax_python_dir) + shprint(sh.cp, '-r', join(python_dir, + 'modules'), crystax_python_dir) + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), + join(crystax_python_dir, 'site-packages')) info('Renaming .so files to reflect cross-compile') - site_packages_dir = 'crystax_python/crystax_python/site-packages' - filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode( - 'utf-8').split('\n')[:-1] - for filen in filens: - parts = filen.split('.') + site_packages_dir = join(crystax_python_dir, "site-packages") + find_ret = shprint( + sh.find, site_packages_dir, '-iname', '*.so') + filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1] + for filename in filenames: + parts = filename.split('.') if len(parts) <= 2: continue - shprint(sh.mv, filen, filen.split('.')[0] + '.so') + shprint(sh.mv, filename, filename.split('.')[0] + '.so') site_packages_dir = join(abspath(curdir), site_packages_dir) if 'sqlite3' not in self.ctx.recipe_build_order: with open('blacklist.txt', 'a') as fileh: fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - self.strip_libraries(arch) self.fry_eggs(site_packages_dir) super(LbryBootstrap, self).run_distribute() + bootstrap = LbryBootstrap() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore b/p4a/pythonforandroid/bootstraps/lbry/build/.gitignore new file mode 100644 index 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 index f74e644..0dee5c8 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties +++ b/p4a/pythonforandroid/bootstraps/lbry/build/ant.properties @@ -16,3 +16,7 @@ # The password will be asked during the build when you use the 'release' target. source.absolute.dir = tmp-src + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py index b5dbbe2..f1e79c4 100755 --- a/p4a/pythonforandroid/bootstraps/lbry/build/build.py +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py @@ -1,9 +1,10 @@ #!/usr/bin/env python2.7 +# coding: utf-8 from __future__ import print_function - -from os.path import dirname, join, isfile, realpath, relpath, split, exists -from os import makedirs, remove +from os.path import ( + dirname, join, isfile, realpath, relpath, split, exists, basename) +from os import makedirs, remove, listdir import os import tarfile import time @@ -11,19 +12,12 @@ import subprocess import shutil from zipfile import ZipFile import sys -import re +from distutils.version import LooseVersion from fnmatch import fnmatch import jinja2 -if os.name == 'nt': - ANDROID = 'android.bat' - ANT = 'ant.bat' -else: - ANDROID = 'android' - ANT = 'ant' - curdir = dirname(__file__) # Try to find a host version of Python that matches our ARM version. @@ -58,6 +52,17 @@ python_files = [] environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) + +def try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + def render(template, dest, **kwargs): '''Using jinja2, render `template` to the filename `dest`, supplying the @@ -109,6 +114,7 @@ def listfiles(d): for fn in listfiles(subdir): yield fn + def make_python_zip(): ''' Search for all the python related files, and construct the pythonXX.zip @@ -125,7 +131,6 @@ def make_python_zip(): global python_files d = realpath(join('private', 'lib', 'python2.7')) - def select(fn): if is_blacklist(fn): return False @@ -152,6 +157,7 @@ def make_python_zip(): zf.write(fn, afn) zf.close() + def make_tar(tfn, source_dirs, ignore_path=[]): ''' Make a zip file `fn` from the contents of source_dis. @@ -213,15 +219,6 @@ def compile_dir(dfn): def make_package(args): - # # Update the project to a recent version. - # try: - # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t', - # 'android-{}'.format(args.sdk_version)]) - # except (OSError, IOError): - # print('An error occured while calling', ANDROID, 'update') - # print('Your PATH must include android tools.') - # sys.exit(-1) - # Ignore warning if the launcher is in args if not args.launcher: if not (exists(join(realpath(args.private), 'main.py')) or @@ -233,18 +230,14 @@ main.py that loads it.''') exit(1) # Delete the old assets. - if exists('assets/public.mp3'): - os.unlink('assets/public.mp3') - - if exists('assets/private.mp3'): - os.unlink('assets/private.mp3') + try_unlink('src/main/assets/public.mp3') + try_unlink('src/main/assets/private.mp3') # In order to speedup import and initial depack, # construct a python27.zip make_python_zip() - # Package up the private and public data. - # AND: Just private for now + # Package up the private data (public not supported). tar_dirs = [args.private] if exists('private'): tar_dirs.append('private') @@ -252,42 +245,21 @@ main.py that loads it.''') tar_dirs.append('crystax_python') if args.private: - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) elif args.launcher: # clean 'None's as a result of main.py path absence tar_dirs = [tdir for tdir in tar_dirs if tdir] - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) - # else: - # make_tar('assets/private.mp3', ['private']) - - # if args.dir: - # make_tar('assets/public.mp3', [args.dir], args.ignore_path) - - - # # Build. - # try: - # for arg in args.command: - # subprocess.check_call([ANT, arg]) - # except (OSError, IOError): - # print 'An error occured while calling', ANT - # print 'Did you install ant on your system ?' - # sys.exit(-1) - + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) # folder name for launcher url_scheme = 'kivy' # Prepare some variables for templating process - if args.launcher: - default_icon = 'templates/launcher-icon.png' - default_presplash = 'templates/launcher-presplash.jpg' - else: - default_icon = 'templates/kivy-icon.png' - default_presplash = 'templates/kivy-presplash.jpg' - shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') - + default_icon = 'templates/kivy-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png') shutil.copy(args.presplash or default_presplash, - 'res/drawable/presplash.jpg') + 'src/main/res/drawable/presplash.jpg') # If extra Java jars were requested, copy them into the libs directory if args.add_jar: @@ -295,7 +267,18 @@ main.py that loads it.''') if not exists(jarname): print('Requested jar does not exist: {}'.format(jarname)) sys.exit(-1) - shutil.copy(jarname, 'libs') + shutil.copy(jarname, 'src/main/libs') + + # if extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) versioned_name = (args.name.replace(' ', '').replace('\'', '') + '-' + args.version) @@ -343,41 +326,57 @@ main.py that loads it.''') service_names.append(name) render( 'Service.tmpl.java', - 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), + 'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), name=name, entrypoint=entrypoint, args=args, foreground=foreground, sticky=sticky, - service_id=sid + 1, - ) + service_id=sid + 1) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + build_tools_versions = listdir(join(sdk_dir, 'build-tools')) + build_tools_versions = sorted(build_tools_versions, + key=LooseVersion) + build_tools_version = build_tools_versions[-1] + render( 'AndroidManifest.tmpl.xml', - 'AndroidManifest.xml', + 'src/main/AndroidManifest.xml', args=args, service=service, service_names=service_names, - url_scheme=url_scheme, - ) + android_api=android_api, + url_scheme=url_scheme) - render( - 'build.tmpl.xml', - 'build.xml', - args=args, - versioned_name=versioned_name) + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(join('src', 'main', 'AndroidManifest.xml'), + 'AndroidManifest.xml') + render( 'strings.tmpl.xml', - 'res/values/strings.xml', + 'src/main/res/values/strings.xml', args=args, url_scheme=url_scheme, - ) - + private_version=str(time.time())) + # add colors.xml render( 'colors.tmpl.xml', - 'res/values/colors.xml', + 'src/main/res/values/colors.xml', args=args, url_scheme=url_scheme, ) @@ -385,33 +384,39 @@ main.py that loads it.''') # add activity_service_control render( 'activity_service_control.xml', - 'res/layout/activity_service_control.xml', + 'src/main/res/layout/activity_service_control.xml', args=args, url_scheme=url_scheme, ) + ## gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + android_api=android_api, + build_tools_version=build_tools_version) + + ## ant build templates + render( + 'build.tmpl.xml', + 'build.xml', + args=args, + versioned_name=versioned_name) + render( 'custom_rules.tmpl.xml', 'custom_rules.xml', args=args) + if args.sign: render('build.properties', 'build.properties') else: if exists('build.properties'): os.remove('build.properties') - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml')) as fileh: - lines = fileh.read() - - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml'), 'w') as fileh: - fileh.write(re.sub(r'"private_version">[0-9\.]*<', - '"private_version">{}<'.format( - str(time.time())), lines)) - - def parse_args(args=None): global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON default_android_api = 12 @@ -486,9 +491,15 @@ tools directory of the Android SDK. help=('Add a Java .jar to the libs, so you can access its ' 'classes with pyjnius. You can specify this ' 'argument more than once to include multiple jars')) + ap.add_argument('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + ## The --sdk option has been removed, it is ignored in favour of + ## --android-api handled by toolchain.py ap.add_argument('--sdk', dest='sdk_version', default=-1, - type=int, help=('Android SDK version to use. Default to ' - 'the value of minsdk')) + type=int, help=('Deprecated argument, does nothing')) ap.add_argument('--minsdk', dest='min_sdk_version', default=default_android_api, type=int, help=('Minimum Android SDK version to use. Default to ' @@ -500,13 +511,14 @@ tools directory of the Android SDK. 'filename containing xml. The filename should be ' 'located relative to the python-for-android ' 'directory')) - ap.add_argument('--with-billing', dest='billing_pubkey', - help='If set, the billing service will be added (not implemented)') ap.add_argument('--service', dest='services', action='append', help='Declare a new service entrypoint: ' 'NAME:PATH_TO_PY[:foreground]') ap.add_argument('--add-source', dest='extra_source_dirs', action='append', help='Include additional source dirs in Java build') + ap.add_argument('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', help='Do not optimise .py files to .pyo.') ap.add_argument('--sign', action='store_true', @@ -521,12 +533,12 @@ tools directory of the Android SDK. if args.name and args.name[0] == '"' and args.name[-1] == '"': args.name = args.name[1:-1] - if args.billing_pubkey: - print('Billing not yet supported in sdl2 bootstrap!') - exit(1) + # if args.sdk_version == -1: + # args.sdk_version = args.min_sdk_version - if args.sdk_version == -1: - args.sdk_version = args.min_sdk_version + if args.sdk_version != -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') if args.permissions is None: args.permissions = [] @@ -540,6 +552,18 @@ tools directory of the Android SDK. if args.services is None: args.services = [] + if args.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + if args.no_compile_pyo: PYTHON = None BLACKLIST_PATTERNS.remove('*.py') @@ -562,5 +586,4 @@ tools directory of the Android SDK. if __name__ == "__main__": - parse_args() diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 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..ac1799f --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/gradlew b/p4a/pythonforandroid/bootstraps/lbry/build/gradlew new file mode 100755 index 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/res/values/colors.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.xml deleted file mode 100644 index 6876df8..0000000 --- a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/colors.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - #3F51B5 - #303F9F - #FF4081 - - #FF0000 - #00C000 - \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml index eed34f3..daebceb 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/res/values/strings.xml @@ -1,10 +1,5 @@ + - LbryControl + SDL App 0.1 - - Running - Service Status - Stopped - START - STOP diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/assets/.gitkeep new file mode 100644 index 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/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/Octal.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarConstants.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarEntry.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarHeader.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarInputStream.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarOutputStream.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kamranzafar/jtar/TarUtils.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiver.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonService.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/PythonUtil.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonEvent.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/concurrency/PythonLock.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/Project.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectAdapter.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/kivy/android/launcher/ProjectChooser.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/libsdl/app/SDLActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/AssetExtract.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/AssetExtract.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/Hardware.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/Hardware.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonActivity.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/PythonService.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/ResourceManager.java b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java similarity index 100% rename from p4a/pythonforandroid/bootstraps/lbry/build/src/org/renpy/android/ResourceManager.java rename to p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/jniLibs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/libs/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/libs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d50bdaae06ee5a8d3f39911f81715abd3bf7b24d GIT binary patch literal 2683 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 literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..0a299eb3cc0273ad1fc260cf0b4a2c35f5d373f5 GIT binary patch literal 1698 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 literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d423dac2624cf0b5dc90821a15362bc29e5a1e6b GIT binary patch literal 6874 zcmZWuXHXMNw@yOuq4y#nO^P5TQloT`4$_N&G^I%=ln8>M1&}I8?;>E}r6yn@fPxe; z^dbb1-XuZkm+yXm-aB{Z+1)ccyU#hZGkebNIZ39*x^&cB)Bpg0PG3*U{CdpzPopHi z?$>qj9RL7VnZDLN%kbIVB5zAe{oDcclj<6nIx(ssC^DDf$B%^{Y70ML);W&St+tcp zRk>?D`*zaV9r4M@lGNpbDF~2YsTd_?2OQo}Dp?MH8Yd_qSWg)zXm{1A^eRuiS?-2K zmT*GH`nidRI^Ut7^Z8;LObFk7{)wSdKTE`@K;C=&{|F83k|H9%pe`-~l2vdx+)v|A z{~-&pX1~g0K9?Tp{c1^R{#F}?cKAC3^3uJ(QU+#nfo{Ot}SnVw?Wx8NRxT2 z#puhP-L24#J)8Mzw&$XE@7@)Vkf46IvEfA^IF&@6o^2bnOD9Jj2t?T1+4iN?*Jneb2FWLP4#Dg@}0kR zu)4NJR#R1V?|aTI>le8F3G@B>GmX<@Sr7ZL$J{U;%`>89b={TcQ{D$BjSIea)-c;` zg?pFtlO72*m6gK6qM{V<=I0&6_Cqvo?VnhMeX!2-n_0-I_lKFB6&fz&_&-pii`ED# z(hNa!%pvE0d5}vq+3kA-g11Vs8cYQE>Ue8%@sc*3PhsWcXq|*apywGEzoJlkDGOtu_yK(? zmc9wxu|tSH!%?rAdie>?Np`$Y3^g5Iv!!xcOa3 zH3}CuIVl=U3|;CAYnoyW=-t#n>V(i4g-?D*r=8*ZXyoO8|A`h`{9_MqK1xQ-TnO|O zp!xWb&dCftPRZ#alDz}Wz6W20xl{AcrPPa>K_x~L8!4Ohw>?JezS(caNfBZ_Uo}w! za1Sk}5S&A8F2~8f`EJ`UI@?C_(c_#)4?A5heXJ#IR+1B*w7Gqs<-MOW)%oakH^oEW znG_?x%Yb85(vng=tcb(?M_(O-gM;%4MQ@*Mp@O?ra%cBh>|ECyCzQ4q-e+u?+J%k^ zE^Y*yb0r)2F~8@%peYsf$zS6RUGA1)ciouo<2asB0`VmxfLx6frK2?sEP%8(C~D^s zyZG-dQ?Bye$f6ed5m?+o?FmZk?4B=4|8Prhr8aj$b<~Q0e8{FpnK$Va;jz7}+sVUk z{{*rW5O)yjutl(I8;3{;Rs-4>^zeddmAQ&-JGOy zJAtD#iAE_Hh(Keh{@D$=F;3=9@B6b&1x%zfS^&uhbyhfq+gRj2)j=fyU2jQC4Y+Rg zrz9JGV{)AS4&xqWbHtF?IlI-P_Mw+w8j(3k%za&@O*7I9FkTY$DegFDo?p+Bm}Z zzmM}xfX(aA-5^ot^7l>NXdj>~4h~cdWV0xGiv4ToswbTE*aeU&{W9;Twi9pyMs67p zMZ3>U_O+RG@s%_w^m^T$p zzmYlA&>qYJ|}XuHmhMF|J`AX7HzCE2rE*!tr_#G1P5-DGHyaa5>KYBj`P1{LpMQ ziGxi6TH)yp7^h&29l3?^g)9l1GRwGAXW;m>q!cXkYWaN7VILj>D#>-9zeF38cQgSd z>O0a$#pI{AdKfxS$eQFIx|_ju%=0P$m7_E>=sj2!^Cw3{?1p}7h4i*lh&3F-Y$4zZ zXF>EhxlBnxlNVx5)2;0PP=i@E ziYGus!~4H%Bf`SV=Lth1Re#vXyl|-#e}@n=3z21IZXX!w0;OsYUDcD;d{BL&ZA%kS=|8$^S^J-nN#}98qKfdlDg^%&^4wBJkfE4ypN1<7X7@14Lx?gP-J*Zb-S& zvqdYj#`36J0B}X@@a4uR`0DIP9*you=CI_aY{C7mazg^7zFh6NWmf5`7)+I|z25mGU>^c0fLC<{Fm!28}60bw~`b7+f* z5t4we{&m1wop5C`E;1gr_&(y2ZOju{zA(d{?4*xlD+olgcJRD+MSr3sb%uxcC)V>M zjAUkp&HN`&6PiZ(^MBLowpNUl+Li7(L%X5K<%kdf1FjLqiS+v z=n3xx2)mFCN(0dw2A5f~KLP0_9YMHNxEK>E1}aRQ%)FxkhXsxdAA~+TzU|H^`|j-~ zz-8RjlB<&JXGjMgExNYcV^4S3;jtWacsRXJnU5I$sxfS4FT{oh3{zH+Pv($p)LoX^ z1|g(`j=1jk3W3@9l7_6u8yE!0$~&-zptujI`8NSub+@?RdbT-*^%t*?@~Qhw0<;!} z5npB2hj3uj-#Zk)L?TLcrLw*6J}Dbpcbvy)wgSX0#C-y?Ia0S>(f|t*-D#R-%arO& zNM_kXHRHBpD(KdyHpkVpYhQ%;kfF5IcnOB|SP{|(C1goP!>fNZQ?kbb?H-=c^qEI#ltqB;gAQqMOxe3OijU%Ab5L~lAd1;sXIJ8 zNt5Ad! zs2!sf!g!6bHHhe2dlO!|pINS&IeBtFNu6ebKrC2f2%X>F!G1iYS0Cn*l zB3mNAQ-;8tv3&MwAgq3D*eXa?5yz}GUla|U^S4MI1)y}zC(X9wuyYT{5L3G8RsGhm zEa~C|fWgqWA0RAyJSbHVMVf~tigj3dSOKJL*Asuns`AEGk$MgxyQjl$>jit~0yg*~ z{Xg%kSek}`!{w2x13t@XUGW>5r*EP*3-mfQir+izKeJ);N5>rH>W83$jxYGYK8H3L z%^r3DdAA%kj$Ec#6hw2CrqAwO#me2qG6>;H{Y~OkbgV>=Z7ZwO8N)F|V%XPz0uZuU zLM7}Z@^+M2T$Zu-`pO6b@SO_edVd<}$&=b&gG{|`*M_vu$^UtD7PGtL(brA!_r3ys zpE+J;?|!HHBq`W_EE9s#xyVW~T<#j;E)dbda4Uyq za%f?k8-TUj@D)QuI4qO_u+V^!*{UO5^x6EJU68x57CUr{pVE}o#cA+iIJD6s<~wga zXycxdROq$Ogkex_?d+=27isz{QWRq{myN)Uc9u#Md2A=;v2={9NC!H}tZ!T{sPF5P z(pW7VyZsDu-~s>J;+IAC@BFU{+vY@7X?(&rMb1&nd}?-pT5|tR*oU{H7_k%Z4r`}f8WMcMMi)L); zdrBASbRyI|%zq1R&l_G}1^tv&lQ>Dpwto2JB_qxpS*9*MrKv*u-B#Dv97>FxQIqNa zxv8R2tH2Mny{m1rzmq~Itv>zUSEbGG4}1k#G`+KhZ2nEX4chpy$SVqP{ZFho?H#y7 z3RQJ$dhQvTN7TFy9`923l~$&8WqZ)^`<>Pl(+khtDMg~8hix;8b7DGg_e5gevMoMv zL`ut7!z+S+oIBPJlY)DX2GVPu%(;$zxjVgz(fru*(mdR)%g_%-1GKCLdVQ&sc0%+4 zIB7EXcGRAH2EnoyH@gUMAimzpxw=~Z)t*2lmxyC2j&778&@{J^i?p`fE;V@@RFS9y z)OF<7TnhN}%_*0eG6?vO1|)gb^_3TiU;5AJ+hp~M%8#;sggVH(iY%4`)XE{x_i143 zM-hKnAG@7)$z4C9=r~(AkbFQd?~}{lhUAE*hauI~H0EJW`gAP*>mBZDkKOx?V{bzT zZ^!w)KE^M6@;7Vva`>t$g!IrrdVc=eTk$;c-aq4lKtT$60U zka>a}rL*wh+Mjs@kAsOVCaLtW4>c`3-vcutE$FwiJ&C4WZe4Jenl#|iyh%o@?I07P z*16~qJ2~h4t?#FLi4gwN)@4+~YL*qf7(r@g0A#(JX|k6=zVGQBCg zxnjYUNZ@%~1?pK#O*kd4qjUREoUFDan7k=Ubb!`i*jI6v?a$!Ru7a;g@v)7fhUo41 z@xuoO6V7i<#SG1m2A$fgj($m{9muX4f>DcF?;o%r)Dkyb!=KXgG(yrynN;9eBhh=`+H2fxg5y4pm@ zx0TCyJM35+&XBXV;mOCt+8+y-v1yD827XRA@Fq1&4_tlnc3E?*#Ka^~(-EDHq3rf; z`@--y2^6m3lgbc5l_^4@@-W$0`a3$lW7m>&XLwMZb-)f*T&wSNRY{}-7FcWzQhJT*DGH11*49DPM@fth&t7>C_eBY zfYgs|{xjd{*{PHY>hlO$*??Q%Z3u`_)^oX4gY+?r(g|6DknEbAuSz`Y_M7)}DwMjl zd@;|LDXZloSk8r?cjXOwS4bCK{nYpiDK{==1(#H=1V(q$zrg$aq6&is5^24vT?dJgk+uQeDIQmB&kX7~VpT7@NwB*e^ zYBvGjM;2iP3)5^ow&6q8EwZ75Oy6oonb&7Oe+rR#B~!@^-8>-BmFL&eTKt;+9J;G0 zY10E-UQPNNGF;8ep%B5iH{YNYV8ar3-`Hiie-~01MRm*TXM;3MHhP(>BZ3#}PHnm_ z**btiK%UMmr?nn8Qc@;iab)%4kS6fr=#Y#SlwWV{o0E?&k$7`-2{~i9b339~DuPn! zTg?i2qLwKw&TqUE0_KW$W++BDXZC;l=aY`XkKOi8>KR7p_rn_J6Ys|tY^D7#)4$w; zxDgX~ZF>3{k$XKh?r6?4l61je!E(9dXAwBlda4)1>)z`_1P z|2QSt2y6HIwuABa2DxvJ&3wgY484ianN3!B!(4aYS;WvzW^N2`C~n%1FUC~`rYIVz zQOt!*Uw+{7pqMDd))?@SM`GPL;wHYbxEHLk>H*|e_3lyW|F zH2ot5V5IUg=IeSKzx&{8X@(+NwDQ2?kInv zFZRRI4RODd3UFG1%1CWrfEH) z5F9BP*EQ@IFu(jlywr0MXiwAus5Tvm|BEOee(%i~P}!vL-gA zFWJz)aMy;ZG{)u4*PCxf!6xTT{$7>uj)dn`yHRd=><(eOtlO|RRHYde17`Nt{*!Ko z@YCnM0mR-b)5N|YyL!d0&%IChBl!6{b`L)x7Z#^iw$tl5ZM&N}h8+MG7XHNom-uJ$ zvjG{Za~|~`IV9eF~PnmFG4gsjcuS=Hb&Q3DlqFW3UaffQ> zm@eqochXKh5`Y-xqi<&D=$*d+DFP< z+PS4b*>4tA(pAkF@aOJ}%4|z)f5|<|r>f<~{VUk7kT@~6feKeJV+a$KzBq{MxNR~! z^Vkp`OtRnBrih!ENYS=by&I8N+GTB(MsDDLS73dou_8!%$H2ocoenquyTg98Kgjzl zdrL;}4QAifo8bm=`=_fR5T$4S$+`nKMQT1SM#wDkw{yUb$%HPS3c8wzF0IzxLDWnI zky=g?x$%?uHqpt#+XNUq_>rrroxMGoDn_xIaLjwS^5oqcOq8Uk;+Ocv1zoG>D{oQTiUY@QS~e{O22Qrjz3Gy@8DsVcD}b& zC12^#A^fZ4m4!_5&nmqK#;i_jmY?sQ&S_UV4uefn)|FeqrTzx4w@U$Aex2^oIo>&`u-R=<#xnqg|M`(tNakI4{s_s2qN+gyzv9#l7- zJ0#(q1)1Vlh9*pLRto0)K1*IUr70w3bs4e9SDy(W-mnq;SoI*iMX+Z(r<=qR#$$0v z>F*e=79o-rhQG;9mzL&LE?r+$m2m9en^j}dcNH2ey|Cf$Aq|g_97v?74xBUm|Jm;U zT-q%TtZr0udxk9P%QM9xKALYmD{iBwy1BI2{Qp9JpWqHy2R2 zk6AMSJZrc)iHQeEZCxGJe}%F#;D3ecYVr&1*4EbU>dW9_n_r-flKQuA1r!djzW!`* z2?>b&Oe#ePfSu{1nf{ zSC_$^Q|KKPg`vSJT#L*|W>*@abvb)PF$Uc-JS@jFoQ}R?A0O3^6{HjFo=PLnO0==j zR8dGJ^yECrf7_F*ZW0gkLfoM{}HAD literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml new file mode 100644 index 0000000..1823b13 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml new file mode 100644 index 0000000..123c4b6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml new file mode 100644 index 0000000..23828e6 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml new file mode 100644 index 0000000..ee54814 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml index f5c35e8..39125e2 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml @@ -19,7 +19,7 @@ /> - + @@ -51,12 +51,7 @@ An example Java class can be found in README-android.txt --> - - {% endfor %} - - {% endfor %} - diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java b/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java index 0b94ea7..3ed10c2 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/Service.tmpl.java @@ -1,5 +1,8 @@ package {{ args.package }}; +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import android.content.Intent; import android.content.Context; import android.app.Notification; @@ -26,13 +29,31 @@ public class Service{{ name|capitalize }} extends PythonService { @Override protected void doStartForeground(Bundle extras) { + Notification notification; Context context = getApplicationContext(); - Notification notification = new Notification(context.getApplicationInfo().icon, - "{{ args.name }}", System.currentTimeMillis()); Intent contextIntent = new Intent(context, PythonActivity.class); PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle("{{ args.name }}"); + builder.setContentText("{{ name| capitalize }}"); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } startForeground({{ service_id }}, notification); } diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle deleted file mode 100644 index 514ba0f..0000000 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/app.build.tmpl.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.model.application' - -model { - android { - compileSdkVersion {{ args.sdk_version }} - buildToolsVersion "23.0.3" - - defaultConfig { - applicationId "{{ args.package }}" - minSdkVersion.apiLevel {{ args.min_sdk_version }} - targetSdkVersion.apiLevel {{ args.sdk_version }} - versionCode {{ args.numeric_version }} - versionName "{{ args.version }}" - - buildConfigFields { - create() { - type "int" - name "VALUE" - value "1" - } - } - } - ndk { - abiFilters.add("armeabi-v7a") - moduleName = "main" - toolchain = "gcc" - toolchainVersion = "4.9" - platformVersion = 16 - stl = "gnustl_shared" - renderscriptNdkMode = false - CFlags.add("-I" + file("src/main/jni/include/python2.7")) - ldFlags.add("-L" + file("src/main/jni/lib")) - ldLibs.addAll(["log", "python2.7"]) - } - // Configures source set directory. - sources { - main { - jniLibs { - dependencies { - library "gnustl_shared" - // add pre-built libraries here and locate them below: - } - } - } - } - } - repositories { - libs(PrebuiltLibraries) { - gnustl_shared { - binaries.withType(SharedLibraryBinary) { - sharedLibraryFile = file("src/main/jniLibs/${targetPlatform.getName()}/libgnustl_shared.so") - } - } - // more here - } - } -} - -// normal project dependencies here \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle new file mode 100644 index 0000000..aaba011 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle @@ -0,0 +1,70 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.1' + } +} + +allprojects { + repositories { + jcenter() + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion {{ android_api }} + buildToolsVersion '{{ build_tools_version }}' + defaultConfig { + minSdkVersion {{ args.min_sdk_version }} + targetSdkVersion {{ android_api }} + versionCode {{ args.numeric_version }} + versionName '{{ args.version }}' + } + + {% if args.sign -%} + signingConfigs { + release { + storeFile file(System.getenv("P4A_RELEASE_KEYSTORE")) + keyAlias System.getenv("P4A_RELEASE_KEYALIAS") + storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD") + keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD") + } + } + {%- endif %} + + buildTypes { + debug { + } + release { + {% if args.sign -%} + signingConfig signingConfigs.release + {%- endif %} + } + } + + sourceSets { + main { + jniLibs.srcDir 'libs' + } + } + +} + +dependencies { + {%- for aar in aars %} + compile(name: '{{ aar }}', ext: 'aar') + {%- endfor -%} + {%- if args.depends -%} + {%- for depend in args.depends %} + compile '{{ depend }}' + {%- endfor %} + {%- endif %} +} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml b/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml index b2de7d6..a6a7eba 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/custom_rules.tmpl.xml @@ -3,9 +3,9 @@ {% if args.launcher %} - + {% else %} - + diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-icon.png index 59a00ba6fff07cec43a4100fdf22f3f679df2349..6ecb013b4434585eef667ce48c97f55265eddeec 100644 GIT binary patch delta 3227 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 L>T(q_W}*KFob!<> diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg b/p4a/pythonforandroid/bootstraps/lbry/build/templates/kivy-presplash.jpg index 161ebc09284183771507c3eeb338cd5a7fefcb9d..c61efa272e88d11f0984a20a436d76028360eeb4 100644 GIT binary patch literal 11584 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�?&S>mLfAa%CJdI`D!ndU) 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 literal 18251 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 {{ args.name }} - 0.1 + {{ private_version }} {{ args.presplash_color }} {{ url_scheme }} - + Running Service Status Stopped diff --git a/p4a/pythonforandroid/bootstraps/pygame/__init__.py b/p4a/pythonforandroid/bootstraps/pygame/__init__.py index cd34040..ec45482 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/__init__.py +++ b/p4a/pythonforandroid/bootstraps/pygame/__init__.py @@ -46,7 +46,6 @@ class PygameBootstrap(Bootstrap): info('Copying python distribution') hostpython = sh.Command(self.ctx.hostpython) - # AND: This *doesn't* need to be in arm env? try: shprint(hostpython, '-OO', '-m', 'compileall', self.ctx.get_python_install_dir(), _tail=10, _filterout="^Listing") @@ -64,7 +63,6 @@ class PygameBootstrap(Bootstrap): shprint(sh.cp, '-a', join('python-install', 'lib'), 'private') shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7')) - # AND: Copylibs stuff should go here shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/') shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/')) diff --git a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java index 321ee60..07f9c88 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java +++ b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java @@ -1,5 +1,8 @@ package org.renpy.android; +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import android.app.Service; import android.os.IBinder; import android.os.Bundle; @@ -54,14 +57,31 @@ public class PythonService extends Service implements Runnable { pythonThread = new Thread(this); pythonThread.start(); + Notification notification; Context context = getApplicationContext(); - Notification notification = new Notification(context.getApplicationInfo().icon, - serviceTitle, - System.currentTimeMillis()); Intent contextIntent = new Intent(context, PythonActivity.class); PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT); - notification.setLatestEventInfo(context, serviceTitle, serviceDescription, pIntent); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(serviceTitle); + builder.setContentText(serviceDescription); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } startForeground(1, notification); return START_NOT_STICKY; diff --git a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java index da7e87e..d88783b 100644 --- a/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java +++ b/p4a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java @@ -706,6 +706,7 @@ public class SDLSurfaceView extends SurfaceView implements SurfaceHolder.Callbac nativeResize(mWidth, mHeight); nativeInitJavaCallbacks(); nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + nativeSetEnv("ANDROID_UNPACK", mFilesDirectory); nativeSetEnv("ANDROID_ARGUMENT", mArgument); nativeSetEnv("ANDROID_APP_PATH", mArgument); nativeSetEnv("PYTHONOPTIMIZE", "2"); diff --git a/p4a/pythonforandroid/bootstraps/sdl2/__init__.py b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py index 210bb98..95748a6 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/p4a/pythonforandroid/bootstraps/sdl2/__init__.py @@ -1,67 +1,81 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir from os.path import join, exists, curdir, abspath from os import walk import glob import sh -class SDL2Bootstrap(Bootstrap): - name = 'sdl2' + +EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx") + + +class SDL2GradleBootstrap(Bootstrap): + name = 'sdl2_gradle' recipe_depends = ['sdl2', ('python2', 'python3crystax')] def run_distribute(self): - info_main('# Creating Android project from build and {} bootstrap'.format( - self.name)) + info_main("# Creating Android project ({})".format(self.name)) - info('This currently just copies the SDL2 build stuff straight from the build dir.') - shprint(sh.rm, '-rf', self.dist_dir) - shprint(sh.cp, '-r', self.build_dir, self.dist_dir) + arch = self.ctx.archs[0] + python_install_dir = self.ctx.get_python_install_dir() + from_crystax = self.ctx.python_recipe.from_crystax + crystax_python_dir = join("crystax_python", "crystax_python") + + if len(self.ctx.archs) > 1: + raise ValueError("SDL2/gradle support only one arch") + + info("Copying SDL2/gradle build for {}".format(arch)) + shprint(sh.rm, "-rf", self.dist_dir) + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environemnt variable (ANDROID_HOME) + # or the local.properties if exists with current_directory(self.dist_dir): with open('local.properties', 'w') as fileh: fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) - arch = self.ctx.archs[0] - if len(self.ctx.archs) > 1: - raise ValueError('built for more than one arch, but bootstrap cannot handle that yet') - info('Bootstrap running with arch {}'.format(arch)) - with current_directory(self.dist_dir): - info('Copying python distribution') + info("Copying Python distribution") - if not exists('private') and not self.ctx.python_recipe.from_crystax: - shprint(sh.mkdir, 'private') - if not exists('crystax_python') and self.ctx.python_recipe.from_crystax: - shprint(sh.mkdir, 'crystax_python') - shprint(sh.mkdir, 'crystax_python/crystax_python') - if not exists('assets'): - shprint(sh.mkdir, 'assets') + if not exists("private") and not from_crystax: + ensure_dir("private") + if not exists("crystax_python") and from_crystax: + ensure_dir(crystax_python_dir) hostpython = sh.Command(self.ctx.hostpython) - if not self.ctx.python_recipe.from_crystax: + if not from_crystax: try: shprint(hostpython, '-OO', '-m', 'compileall', - self.ctx.get_python_install_dir(), + python_install_dir, _tail=10, _filterout="^Listing") except sh.ErrorReturnCode: pass if not exists('python-install'): - shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install') + shprint( + sh.cp, '-a', python_install_dir, './python-install') self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) - self.distribute_aars(arch) - self.distribute_javaclasses(self.ctx.javaclass_dir) + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) - if not self.ctx.python_recipe.from_crystax: - info('Filling private directory') - if not exists(join('private', 'lib')): - info('private/lib does not exist, making') - shprint(sh.cp, '-a', join('python-install', 'lib'), 'private') - shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7')) + if not from_crystax: + info("Filling private directory") + if not exists(join("private", "lib")): + info("private/lib does not exist, making") + shprint(sh.cp, "-a", + join("python-install", "lib"), "private") + shprint(sh.mkdir, "-p", + join("private", "include", "python2.7")) - # AND: Copylibs stuff should go here - if exists(join('libs', arch.arch, 'libpymodules.so')): - shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/') - shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/')) + libpymodules_fn = join("libs", arch.arch, "libpymodules.so") + if exists(libpymodules_fn): + shprint(sh.mv, libpymodules_fn, 'private/') + shprint(sh.cp, + join('python-install', 'include', + 'python2.7', 'pyconfig.h'), + join('private', 'include', 'python2.7/')) info('Removing some unwanted files') shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so')) @@ -70,54 +84,53 @@ class SDL2Bootstrap(Bootstrap): libdir = join(self.dist_dir, 'private', 'lib', 'python2.7') site_packages_dir = join(libdir, 'site-packages') with current_directory(libdir): - # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.'))) removes = [] - for dirname, something, filens in walk('.'): - for filename in filens: - for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'): + for dirname, root, filenames in walk("."): + for filename in filenames: + for suffix in EXCLUDE_EXTS: if filename.endswith(suffix): removes.append(filename) shprint(sh.rm, '-f', *removes) info('Deleting some other stuff not used on android') # To quote the original distribute.sh, 'well...' - # shprint(sh.rm, '-rf', 'ctypes') shprint(sh.rm, '-rf', 'lib2to3') shprint(sh.rm, '-rf', 'idlelib') for filename in glob.glob('config/libpython*.a'): shprint(sh.rm, '-f', filename) shprint(sh.rm, '-rf', 'config/python.o') - # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so') - # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so') else: # Python *is* loaded from crystax ndk_dir = self.ctx.ndk_dir py_recipe = self.ctx.python_recipe - python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version, - 'libs', arch.arch) - - shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python') - shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python') - shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages') + python_dir = join(ndk_dir, 'sources', 'python', + py_recipe.version, 'libs', arch.arch) + shprint(sh.cp, '-r', join(python_dir, + 'stdlib.zip'), crystax_python_dir) + shprint(sh.cp, '-r', join(python_dir, + 'modules'), crystax_python_dir) + shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), + join(crystax_python_dir, 'site-packages')) info('Renaming .so files to reflect cross-compile') - site_packages_dir = 'crystax_python/crystax_python/site-packages' - filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode( - 'utf-8').split('\n')[:-1] - for filen in filens: - parts = filen.split('.') + site_packages_dir = join(crystax_python_dir, "site-packages") + find_ret = shprint( + sh.find, site_packages_dir, '-iname', '*.so') + filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1] + for filename in filenames: + parts = filename.split('.') if len(parts) <= 2: continue - shprint(sh.mv, filen, filen.split('.')[0] + '.so') + shprint(sh.mv, filename, filename.split('.')[0] + '.so') site_packages_dir = join(abspath(curdir), site_packages_dir) if 'sqlite3' not in self.ctx.recipe_build_order: with open('blacklist.txt', 'a') as fileh: fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - self.strip_libraries(arch) self.fry_eggs(site_packages_dir) - super(SDL2Bootstrap, self).run_distribute() + super(SDL2GradleBootstrap, self).run_distribute() -bootstrap = SDL2Bootstrap() + +bootstrap = SDL2GradleBootstrap() diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore new file mode 100644 index 0000000..a1fc39c --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/.gitignore @@ -0,0 +1,14 @@ +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties b/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties index f74e644..0dee5c8 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/ant.properties @@ -16,3 +16,7 @@ # The password will be asked during the build when you use the 'release' target. source.absolute.dir = tmp-src + +resource.absolute.dir = src/main/res + +asset.absolute.dir = src/main/assets diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/build.py b/p4a/pythonforandroid/bootstraps/sdl2/build/build.py index 4a7f466..12f7042 100755 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/build.py +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/build.py @@ -1,9 +1,10 @@ #!/usr/bin/env python2.7 +# coding: utf-8 from __future__ import print_function - -from os.path import dirname, join, isfile, realpath, relpath, split, exists -from os import makedirs, remove +from os.path import ( + dirname, join, isfile, realpath, relpath, split, exists, basename) +from os import makedirs, remove, listdir import os import tarfile import time @@ -11,19 +12,12 @@ import subprocess import shutil from zipfile import ZipFile import sys -import re +from distutils.version import LooseVersion from fnmatch import fnmatch import jinja2 -if os.name == 'nt': - ANDROID = 'android.bat' - ANT = 'ant.bat' -else: - ANDROID = 'android' - ANT = 'ant' - curdir = dirname(__file__) # Try to find a host version of Python that matches our ARM version. @@ -58,6 +52,17 @@ python_files = [] environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) + +def try_unlink(fn): + if exists(fn): + os.unlink(fn) + + +def ensure_dir(path): + if not exists(path): + makedirs(path) + + def render(template, dest, **kwargs): '''Using jinja2, render `template` to the filename `dest`, supplying the @@ -109,6 +114,7 @@ def listfiles(d): for fn in listfiles(subdir): yield fn + def make_python_zip(): ''' Search for all the python related files, and construct the pythonXX.zip @@ -125,7 +131,6 @@ def make_python_zip(): global python_files d = realpath(join('private', 'lib', 'python2.7')) - def select(fn): if is_blacklist(fn): return False @@ -152,6 +157,7 @@ def make_python_zip(): zf.write(fn, afn) zf.close() + def make_tar(tfn, source_dirs, ignore_path=[]): ''' Make a zip file `fn` from the contents of source_dis. @@ -213,15 +219,6 @@ def compile_dir(dfn): def make_package(args): - # # Update the project to a recent version. - # try: - # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t', - # 'android-{}'.format(args.sdk_version)]) - # except (OSError, IOError): - # print('An error occured while calling', ANDROID, 'update') - # print('Your PATH must include android tools.') - # sys.exit(-1) - # Ignore warning if the launcher is in args if not args.launcher: if not (exists(join(realpath(args.private), 'main.py')) or @@ -233,18 +230,14 @@ main.py that loads it.''') exit(1) # Delete the old assets. - if exists('assets/public.mp3'): - os.unlink('assets/public.mp3') - - if exists('assets/private.mp3'): - os.unlink('assets/private.mp3') + try_unlink('src/main/assets/public.mp3') + try_unlink('src/main/assets/private.mp3') # In order to speedup import and initial depack, # construct a python27.zip make_python_zip() - # Package up the private and public data. - # AND: Just private for now + # Package up the private data (public not supported). tar_dirs = [args.private] if exists('private'): tar_dirs.append('private') @@ -252,42 +245,21 @@ main.py that loads it.''') tar_dirs.append('crystax_python') if args.private: - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) elif args.launcher: # clean 'None's as a result of main.py path absence tar_dirs = [tdir for tdir in tar_dirs if tdir] - make_tar('assets/private.mp3', tar_dirs, args.ignore_path) - # else: - # make_tar('assets/private.mp3', ['private']) - - # if args.dir: - # make_tar('assets/public.mp3', [args.dir], args.ignore_path) - - - # # Build. - # try: - # for arg in args.command: - # subprocess.check_call([ANT, arg]) - # except (OSError, IOError): - # print 'An error occured while calling', ANT - # print 'Did you install ant on your system ?' - # sys.exit(-1) - + make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path) # folder name for launcher url_scheme = 'kivy' # Prepare some variables for templating process - if args.launcher: - default_icon = 'templates/launcher-icon.png' - default_presplash = 'templates/launcher-presplash.jpg' - else: - default_icon = 'templates/kivy-icon.png' - default_presplash = 'templates/kivy-presplash.jpg' - shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') - + default_icon = 'templates/kivy-icon.png' + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png') shutil.copy(args.presplash or default_presplash, - 'res/drawable/presplash.jpg') + 'src/main/res/drawable/presplash.jpg') # If extra Java jars were requested, copy them into the libs directory if args.add_jar: @@ -295,7 +267,18 @@ main.py that loads it.''') if not exists(jarname): print('Requested jar does not exist: {}'.format(jarname)) sys.exit(-1) - shutil.copy(jarname, 'libs') + shutil.copy(jarname, 'src/main/libs') + + # if extra aar were requested, copy them into the libs directory + aars = [] + if args.add_aar: + ensure_dir("libs") + for aarname in args.add_aar: + if not exists(aarname): + print('Requested aar does not exists: {}'.format(aarname)) + sys.exit(-1) + shutil.copy(aarname, 'libs') + aars.append(basename(aarname).rsplit('.', 1)[0]) versioned_name = (args.name.replace(' ', '').replace('\'', '') + '-' + args.version) @@ -343,59 +326,81 @@ main.py that loads it.''') service_names.append(name) render( 'Service.tmpl.java', - 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), + 'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()), name=name, entrypoint=entrypoint, args=args, foreground=foreground, sticky=sticky, - service_id=sid + 1, - ) + service_id=sid + 1) + + # Find the SDK directory and target API + with open('project.properties', 'r') as fileh: + target = fileh.read().strip() + android_api = target.split('-')[1] + with open('local.properties', 'r') as fileh: + sdk_dir = fileh.read().strip() + sdk_dir = sdk_dir[8:] + + # Try to build with the newest available build tools + build_tools_versions = listdir(join(sdk_dir, 'build-tools')) + build_tools_versions = sorted(build_tools_versions, + key=LooseVersion) + build_tools_version = build_tools_versions[-1] + render( 'AndroidManifest.tmpl.xml', - 'AndroidManifest.xml', + 'src/main/AndroidManifest.xml', args=args, service=service, service_names=service_names, - url_scheme=url_scheme, - ) + android_api=android_api, + url_scheme=url_scheme) + # Copy the AndroidManifest.xml to the dist root dir so that ant + # can also use it + if exists('AndroidManifest.xml'): + remove('AndroidManifest.xml') + shutil.copy(join('src', 'main', 'AndroidManifest.xml'), + 'AndroidManifest.xml') + + + render( + 'strings.tmpl.xml', + 'src/main/res/values/strings.xml', + args=args, + url_scheme=url_scheme, + private_version=str(time.time())) + + ## gradle build templates + render( + 'build.tmpl.gradle', + 'build.gradle', + args=args, + aars=aars, + android_api=android_api, + build_tools_version=build_tools_version) + + ## ant build templates render( 'build.tmpl.xml', 'build.xml', args=args, versioned_name=versioned_name) - render( - 'strings.tmpl.xml', - 'res/values/strings.xml', - args=args, - url_scheme=url_scheme, - ) - render( 'custom_rules.tmpl.xml', 'custom_rules.xml', args=args) + if args.sign: render('build.properties', 'build.properties') else: if exists('build.properties'): os.remove('build.properties') - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml')) as fileh: - lines = fileh.read() - - with open(join(dirname(__file__), 'res', - 'values', 'strings.xml'), 'w') as fileh: - fileh.write(re.sub(r'"private_version">[0-9\.]*<', - '"private_version">{}<'.format( - str(time.time())), lines)) - - def parse_args(args=None): global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON default_android_api = 12 @@ -470,9 +475,15 @@ tools directory of the Android SDK. help=('Add a Java .jar to the libs, so you can access its ' 'classes with pyjnius. You can specify this ' 'argument more than once to include multiple jars')) + ap.add_argument('--add-aar', dest='add_aar', action='append', + help=('Add an aar dependency manually')) + ap.add_argument('--depend', dest='depends', action='append', + help=('Add a external dependency ' + '(eg: com.android.support:appcompat-v7:19.0.1)')) + ## The --sdk option has been removed, it is ignored in favour of + ## --android-api handled by toolchain.py ap.add_argument('--sdk', dest='sdk_version', default=-1, - type=int, help=('Android SDK version to use. Default to ' - 'the value of minsdk')) + type=int, help=('Deprecated argument, does nothing')) ap.add_argument('--minsdk', dest='min_sdk_version', default=default_android_api, type=int, help=('Minimum Android SDK version to use. Default to ' @@ -484,13 +495,14 @@ tools directory of the Android SDK. 'filename containing xml. The filename should be ' 'located relative to the python-for-android ' 'directory')) - ap.add_argument('--with-billing', dest='billing_pubkey', - help='If set, the billing service will be added (not implemented)') ap.add_argument('--service', dest='services', action='append', help='Declare a new service entrypoint: ' 'NAME:PATH_TO_PY[:foreground]') ap.add_argument('--add-source', dest='extra_source_dirs', action='append', help='Include additional source dirs in Java build') + ap.add_argument('--try-system-python-compile', dest='try_system_python_compile', + action='store_true', + help='Use the system python during compileall if possible.') ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true', help='Do not optimise .py files to .pyo.') ap.add_argument('--sign', action='store_true', @@ -505,12 +517,12 @@ tools directory of the Android SDK. if args.name and args.name[0] == '"' and args.name[-1] == '"': args.name = args.name[1:-1] - if args.billing_pubkey: - print('Billing not yet supported in sdl2 bootstrap!') - exit(1) + # if args.sdk_version == -1: + # args.sdk_version = args.min_sdk_version - if args.sdk_version == -1: - args.sdk_version = args.min_sdk_version + if args.sdk_version != -1: + print('WARNING: Received a --sdk argument, but this argument is ' + 'deprecated and does nothing.') if args.permissions is None: args.permissions = [] @@ -524,6 +536,18 @@ tools directory of the Android SDK. if args.services is None: args.services = [] + if args.try_system_python_compile: + # Hardcoding python2.7 is okay for now, as python3 skips the + # compilation anyway + if not exists('crystax_python'): + python_executable = 'python2.7' + try: + subprocess.call([python_executable, '--version']) + except (OSError, subprocess.CalledProcessError): + pass + else: + PYTHON = python_executable + if args.no_compile_pyo: PYTHON = None BLACKLIST_PATTERNS.remove('*.py') @@ -546,5 +570,4 @@ tools directory of the Android SDK. if __name__ == "__main__": - parse_args() diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 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/sdl2/build/gradle/wrapper/gradle-wrapper.properties b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac1799f --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 17:19:02 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c index 7582348..121d925 100644 --- a/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c @@ -84,6 +84,11 @@ int main(int argc, char *argv[]) { setenv("ANDROID_APP_PATH", env_argument, 1); env_entrypoint = getenv("ANDROID_ENTRYPOINT"); env_logname = getenv("PYTHON_NAME"); + + if (!getenv("ANDROID_UNPACK")) { + /* ANDROID_UNPACK currently isn't set in services */ + setenv("ANDROID_UNPACK", env_argument, 1); + } if (env_logname == NULL) { env_logname = "python"; @@ -104,12 +109,15 @@ int main(int argc, char *argv[]) { LOGP("Preparing to initialize python"); - if (dir_exists("crystax_python/")) { + char crystax_python_dir[256]; + snprintf(crystax_python_dir, 256, + "%s/crystax_python", getenv("ANDROID_UNPACK")); + if (dir_exists(crystax_python_dir)) { LOGP("crystax_python exists"); char paths[256]; snprintf(paths, 256, - "%s/crystax_python/stdlib.zip:%s/crystax_python/modules", - env_argument, env_argument); + "%s/stdlib.zip:%s/modules", + crystax_python_dir, crystax_python_dir); /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument, * env_argument); */ LOGP("calculated paths to be..."); @@ -166,11 +174,11 @@ int main(int argc, char *argv[]) { " argument ]\n"); } - if (dir_exists("crystax_python")) { + if (dir_exists(crystax_python_dir)) { char add_site_packages_dir[256]; snprintf(add_site_packages_dir, 256, - "sys.path.append('%s/crystax_python/site-packages')", - env_argument); + "sys.path.append('%s/site-packages')", + crystax_python_dir); PyRun_SimpleString("import sys\n" "sys.argv = ['notaninterpreterreally']\n" diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java new file mode 100755 index 0000000..dd10624 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java @@ -0,0 +1,141 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class Octal { + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal string. + */ + public static long parseOctal(byte[] header, int offset, int length) { + long result = 0; + boolean stillPadding = true; + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + + if (header[i] == (byte) ' ' || header[i] == '0') { + if (stillPadding) + continue; + + if (header[i] == (byte) ' ') + break; + } + + stillPadding = false; + + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes(long value, byte[] buf, int offset, int length) { + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = (byte) ' '; + --idx; + + if (value == 0) { + buf[offset + idx] = (byte) '0'; + --idx; + } else { + for (long val = value; idx >= 0 && val > 0; --idx) { + buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) ); + val = val >> 3; + } + } + + for (; idx >= 0; --idx) { + buf[offset + idx] = (byte) ' '; + } + + return offset + length; + } + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) { + getOctalBytes( value, buf, offset, length ); + buf[offset + length - 1] = (byte) ' '; + buf[offset + length - 2] = 0; + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param value + * @param buf + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) { + byte[] temp = new byte[length + 1]; + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + return offset + length; + } + +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java new file mode 100755 index 0000000..4611e20 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java @@ -0,0 +1,28 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +/** + * @author Kamran Zafar + * + */ +public class TarConstants { + public static final int EOF_BLOCK = 1024; + public static final int DATA_BLOCK = 512; + public static final int HEADER_BLOCK = 512; +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java new file mode 100755 index 0000000..fe01db4 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java @@ -0,0 +1,284 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; +import java.util.Date; + +/** + * @author Kamran Zafar + * + */ +public class TarEntry { + protected File file; + protected TarHeader header; + + private TarEntry() { + this.file = null; + header = new TarHeader(); + } + + public TarEntry(File file, String entryName) { + this(); + this.file = file; + this.extractTarHeader(entryName); + } + + public TarEntry(byte[] headerBuf) { + this(); + this.parseTarHeader(headerBuf); + } + + /** + * Constructor to create an entry from an existing TarHeader object. + * + * This method is useful to add new entries programmatically (e.g. for + * adding files or directories that do not exist in the file system). + * + * @param header + * + */ + public TarEntry(TarHeader header) { + this.file = null; + this.header = header; + } + + public boolean equals(TarEntry it) { + return header.name.toString().equals(it.header.name.toString()); + } + + public boolean isDescendent(TarEntry desc) { + return desc.header.name.toString().startsWith(header.name.toString()); + } + + public TarHeader getHeader() { + return header; + } + + public String getName() { + String name = header.name.toString(); + if (header.namePrefix != null && !header.namePrefix.toString().equals("")) { + name = header.namePrefix.toString() + "/" + name; + } + + return name; + } + + public void setName(String name) { + header.name = new StringBuffer(name); + } + + public int getUserId() { + return header.userId; + } + + public void setUserId(int userId) { + header.userId = userId; + } + + public int getGroupId() { + return header.groupId; + } + + public void setGroupId(int groupId) { + header.groupId = groupId; + } + + public String getUserName() { + return header.userName.toString(); + } + + public void setUserName(String userName) { + header.userName = new StringBuffer(userName); + } + + public String getGroupName() { + return header.groupName.toString(); + } + + public void setGroupName(String groupName) { + header.groupName = new StringBuffer(groupName); + } + + public void setIds(int userId, int groupId) { + this.setUserId(userId); + this.setGroupId(groupId); + } + + public void setModTime(long time) { + header.modTime = time / 1000; + } + + public void setModTime(Date time) { + header.modTime = time.getTime() / 1000; + } + + public Date getModTime() { + return new Date(header.modTime * 1000); + } + + public File getFile() { + return this.file; + } + + public long getSize() { + return header.size; + } + + public void setSize(long size) { + header.size = size; + } + + /** + * Checks if the org.kamrazafar.jtar entry is a directory + * + * @return + */ + public boolean isDirectory() { + if (this.file != null) + return this.file.isDirectory(); + + if (header != null) { + if (header.linkFlag == TarHeader.LF_DIR) + return true; + + if (header.name.toString().endsWith("/")) + return true; + } + + return false; + } + + /** + * Extract header from File + * + * @param entryName + */ + public void extractTarHeader(String entryName) { + header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory()); + } + + /** + * Calculate checksum + * + * @param buf + * @return + */ + public long computeCheckSum(byte[] buf) { + long sum = 0; + + for (int i = 0; i < buf.length; ++i) { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Writes the header to the byte buffer + * + * @param outbuf + */ + public void writeEntryHeader(byte[] outbuf) { + int offset = 0; + + offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN); + offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN); + offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN); + offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN); + + long size = header.size; + + offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN); + offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN); + + int csOffset = offset; + for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) + outbuf[offset++] = (byte) ' '; + + outbuf[offset++] = header.linkFlag; + + offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN); + offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN); + offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN); + offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN); + offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX); + + for (; offset < outbuf.length;) + outbuf[offset++] = 0; + + long checkSum = this.computeCheckSum(outbuf); + + Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN); + } + + /** + * Parses the tar header to the byte buffer + * + * @param header + * @param bh + */ + public void parseTarHeader(byte[] bh) { + int offset = 0; + + header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN); + offset += TarHeader.MODELEN; + + header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN); + offset += TarHeader.UIDLEN; + + header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN); + offset += TarHeader.GIDLEN; + + header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN); + offset += TarHeader.SIZELEN; + + header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN); + offset += TarHeader.MODTIMELEN; + + header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN); + offset += TarHeader.CHKSUMLEN; + + header.linkFlag = bh[offset++]; + + header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN); + offset += TarHeader.NAMELEN; + + header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN); + offset += TarHeader.USTAR_MAGICLEN; + + header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN); + offset += TarHeader.USTAR_USER_NAMELEN; + + header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN); + offset += TarHeader.USTAR_GROUP_NAMELEN; + + header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN); + offset += TarHeader.USTAR_DEVLEN; + + header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX); + } +} \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java new file mode 100755 index 0000000..b9d3a86 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java @@ -0,0 +1,243 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * Header + * + *
+ * Offset  Size     Field
+ * 0       100      File name
+ * 100     8        File mode
+ * 108     8        Owner's numeric user ID
+ * 116     8        Group's numeric user ID
+ * 124     12       File size in bytes
+ * 136     12       Last modification time in numeric Unix time format
+ * 148     8        Checksum for header block
+ * 156     1        Link indicator (file type)
+ * 157     100      Name of linked file
+ * 
+ * + * + * File Types + * + *
+ * Value        Meaning
+ * '0'          Normal file
+ * (ASCII NUL)  Normal file (now obsolete)
+ * '1'          Hard link
+ * '2'          Symbolic link
+ * '3'          Character special
+ * '4'          Block special
+ * '5'          Directory
+ * '6'          FIFO
+ * '7'          Contigous
+ * 
+ * + * + * + * Ustar header + * + *
+ * Offset  Size    Field
+ * 257     6       UStar indicator "ustar"
+ * 263     2       UStar version "00"
+ * 265     32      Owner user name
+ * 297     32      Owner group name
+ * 329     8       Device major number
+ * 337     8       Device minor number
+ * 345     155     Filename prefix
+ * 
+ */ + +public class TarHeader { + + /* + * Header + */ + public static final int NAMELEN = 100; + public static final int MODELEN = 8; + public static final int UIDLEN = 8; + public static final int GIDLEN = 8; + public static final int SIZELEN = 12; + public static final int MODTIMELEN = 12; + public static final int CHKSUMLEN = 8; + public static final byte LF_OLDNORM = 0; + + /* + * File Types + */ + public static final byte LF_NORMAL = (byte) '0'; + public static final byte LF_LINK = (byte) '1'; + public static final byte LF_SYMLINK = (byte) '2'; + public static final byte LF_CHR = (byte) '3'; + public static final byte LF_BLK = (byte) '4'; + public static final byte LF_DIR = (byte) '5'; + public static final byte LF_FIFO = (byte) '6'; + public static final byte LF_CONTIG = (byte) '7'; + + /* + * Ustar header + */ + + public static final String USTAR_MAGIC = "ustar"; // POSIX + + public static final int USTAR_MAGICLEN = 8; + public static final int USTAR_USER_NAMELEN = 32; + public static final int USTAR_GROUP_NAMELEN = 32; + public static final int USTAR_DEVLEN = 8; + public static final int USTAR_FILENAME_PREFIX = 155; + + // Header values + public StringBuffer name; + public int mode; + public int userId; + public int groupId; + public long size; + public long modTime; + public int checkSum; + public byte linkFlag; + public StringBuffer linkName; + public StringBuffer magic; // ustar indicator and version + public StringBuffer userName; + public StringBuffer groupName; + public int devMajor; + public int devMinor; + public StringBuffer namePrefix; + + public TarHeader() { + this.magic = new StringBuffer(TarHeader.USTAR_MAGIC); + + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty("user.name", ""); + + if (user.length() > 31) + user = user.substring(0, 31); + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer(user); + this.groupName = new StringBuffer(""); + this.namePrefix = new StringBuffer(); + } + + /** + * Parse an entry name from a header buffer. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName(byte[] header, int offset, int length) { + StringBuffer result = new StringBuffer(length); + + int end = offset + length; + for (int i = offset; i < end; ++i) { + if (header[i] == 0) + break; + result.append((char) header[i]); + } + + return result; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param name + * @param header + * The header buffer from which to parse. + * @param offset + * The offset into the buffer from which to parse. + * @param length + * The number of header bytes to parse. + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) { + int i; + + for (i = 0; i < length && i < name.length(); ++i) { + buf[offset + i] = (byte) name.charAt(i); + } + + for (; i < length; ++i) { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Creates a new header for a file/directory entry. + * + * + * @param name + * File name + * @param size + * File size in bytes + * @param modTime + * Last modification time in numeric Unix time format + * @param dir + * Is directory + * + * @return + */ + public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) { + String name = entryName; + name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/'); + + TarHeader header = new TarHeader(); + header.linkName = new StringBuffer(""); + + if (name.length() > 100) { + header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/'))); + header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1)); + } else { + header.name = new StringBuffer(name); + } + + if (dir) { + header.mode = 040755; + header.linkFlag = TarHeader.LF_DIR; + if (header.name.charAt(header.name.length() - 1) != '/') { + header.name.append("/"); + } + header.size = 0; + } else { + header.mode = 0100644; + header.linkFlag = TarHeader.LF_NORMAL; + header.size = size; + } + + header.modTime = modTime; + header.checkSum = 0; + header.devMajor = 0; + header.devMinor = 0; + + return header; + } +} \ No newline at end of file diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java new file mode 100755 index 0000000..ec50a1b --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java @@ -0,0 +1,249 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Kamran Zafar + * + */ +public class TarInputStream extends FilterInputStream { + + private static final int SKIP_BUFFER_SIZE = 2048; + private TarEntry currentEntry; + private long currentFileSize; + private long bytesRead; + private boolean defaultSkip = false; + + public TarInputStream(InputStream in) { + super(in); + currentFileSize = 0; + bytesRead = 0; + } + + @Override + public boolean markSupported() { + return false; + } + + /** + * Not supported + * + */ + @Override + public synchronized void mark(int readlimit) { + } + + /** + * Not supported + * + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + /** + * Read a byte + * + * @see java.io.FilterInputStream#read() + */ + @Override + public int read() throws IOException { + byte[] buf = new byte[1]; + + int res = this.read(buf, 0, 1); + + if (res != -1) { + return 0xFF & buf[0]; + } + + return res; + } + + /** + * Checks if the bytes being read exceed the entry size and adjusts the byte + * array length. Updates the byte counters + * + * + * @see java.io.FilterInputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (currentEntry != null) { + if (currentFileSize == currentEntry.getSize()) { + return -1; + } else if ((currentEntry.getSize() - currentFileSize) < len) { + len = (int) (currentEntry.getSize() - currentFileSize); + } + } + + int br = super.read(b, off, len); + + if (br != -1) { + if (currentEntry != null) { + currentFileSize += br; + } + + bytesRead += br; + } + + return br; + } + + /** + * Returns the next entry in the tar file + * + * @return TarEntry + * @throws IOException + */ + public TarEntry getNextEntry() throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + byte[] theader = new byte[TarConstants.HEADER_BLOCK]; + int tr = 0; + + // Read full header + while (tr < TarConstants.HEADER_BLOCK) { + int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr); + + if (res < 0) { + break; + } + + System.arraycopy(theader, 0, header, tr, res); + tr += res; + } + + // Check if record is null + boolean eof = true; + for (byte b : header) { + if (b != 0) { + eof = false; + break; + } + } + + if (!eof) { + currentEntry = new TarEntry(header); + } + + return currentEntry; + } + + /** + * Returns the current offset (in bytes) from the beginning of the stream. + * This can be used to find out at which point in a tar file an entry's content begins, for instance. + */ + public long getCurrentOffset() { + return bytesRead; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + // Not fully read, skip rest of the bytes + long bs = 0; + while (bs < currentEntry.getSize() - currentFileSize) { + long res = skip(currentEntry.getSize() - currentFileSize - bs); + + if (res == 0 && currentEntry.getSize() - currentFileSize > 0) { + // I suspect file corruption + throw new IOException("Possible tar file corruption"); + } + + bs += res; + } + } + + currentEntry = null; + currentFileSize = 0L; + skipPad(); + } + } + + /** + * Skips the pad at the end of each tar entry file content + * + * @throws IOException + */ + protected void skipPad() throws IOException { + if (bytesRead > 0) { + int extra = (int) (bytesRead % TarConstants.DATA_BLOCK); + + if (extra > 0) { + long bs = 0; + while (bs < TarConstants.DATA_BLOCK - extra) { + long res = skip(TarConstants.DATA_BLOCK - extra - bs); + bs += res; + } + } + } + } + + /** + * Skips 'n' bytes on the InputStream
+ * Overrides default implementation of skip + * + */ + @Override + public long skip(long n) throws IOException { + if (defaultSkip) { + // use skip method of parent stream + // may not work if skip not implemented by parent + long bs = super.skip(n); + bytesRead += bs; + + return bs; + } + + if (n <= 0) { + return 0; + } + + long left = n; + byte[] sBuff = new byte[SKIP_BUFFER_SIZE]; + + while (left > 0) { + int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE)); + if (res < 0) { + break; + } + left -= res; + } + + return n - left; + } + + public boolean isDefaultSkip() { + return defaultSkip; + } + + public void setDefaultSkip(boolean defaultSkip) { + this.defaultSkip = defaultSkip; + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java new file mode 100755 index 0000000..ffdfe87 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java @@ -0,0 +1,163 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +/** + * @author Kamran Zafar + * + */ +public class TarOutputStream extends OutputStream { + private final OutputStream out; + private long bytesWritten; + private long currentFileSize; + private TarEntry currentEntry; + + public TarOutputStream(OutputStream out) { + this.out = out; + bytesWritten = 0; + currentFileSize = 0; + } + + public TarOutputStream(final File fout) throws FileNotFoundException { + this.out = new BufferedOutputStream(new FileOutputStream(fout)); + bytesWritten = 0; + currentFileSize = 0; + } + + /** + * Opens a file for writing. + */ + public TarOutputStream(final File fout, final boolean append) throws IOException { + @SuppressWarnings("resource") + RandomAccessFile raf = new RandomAccessFile(fout, "rw"); + final long fileSize = fout.length(); + if (append && fileSize > TarConstants.EOF_BLOCK) { + raf.seek(fileSize - TarConstants.EOF_BLOCK); + } + out = new BufferedOutputStream(new FileOutputStream(raf.getFD())); + } + + /** + * Appends the EOF record and closes the stream + * + * @see java.io.FilterOutputStream#close() + */ + @Override + public void close() throws IOException { + closeCurrentEntry(); + write( new byte[TarConstants.EOF_BLOCK] ); + out.close(); + } + /** + * Writes a byte to the stream and updates byte counters + * + * @see java.io.FilterOutputStream#write(int) + */ + @Override + public void write(int b) throws IOException { + out.write( b ); + bytesWritten += 1; + + if (currentEntry != null) { + currentFileSize += 1; + } + } + + /** + * Checks if the bytes being written exceed the current entry size. + * + * @see java.io.FilterOutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (currentEntry != null && !currentEntry.isDirectory()) { + if (currentEntry.getSize() < currentFileSize + len) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] size[" + + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len ) + + "] being written." ); + } + } + + out.write( b, off, len ); + + bytesWritten += len; + + if (currentEntry != null) { + currentFileSize += len; + } + } + + /** + * Writes the next tar entry header on the stream + * + * @param entry + * @throws IOException + */ + public void putNextEntry(TarEntry entry) throws IOException { + closeCurrentEntry(); + + byte[] header = new byte[TarConstants.HEADER_BLOCK]; + entry.writeEntryHeader( header ); + + write( header ); + + currentEntry = entry; + } + + /** + * Closes the current tar entry + * + * @throws IOException + */ + protected void closeCurrentEntry() throws IOException { + if (currentEntry != null) { + if (currentEntry.getSize() > currentFileSize) { + throw new IOException( "The current entry[" + currentEntry.getName() + "] of size[" + + currentEntry.getSize() + "] has not been fully written." ); + } + + currentEntry = null; + currentFileSize = 0; + + pad(); + } + } + + /** + * Pads the last content block + * + * @throws IOException + */ + protected void pad() throws IOException { + if (bytesWritten > 0) { + int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK ); + + if (extra > 0) { + write( new byte[TarConstants.DATA_BLOCK - extra] ); + } + } + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java new file mode 100755 index 0000000..5016576 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java @@ -0,0 +1,96 @@ +/** + * Copyright 2012 Kamran Zafar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.kamranzafar.jtar; + +import java.io.File; + +/** + * @author Kamran + * + */ +public class TarUtils { + /** + * Determines the tar file size of the given folder/file path + * + * @param path + * @return + */ + public static long calculateTarSize(File path) { + return tarSize(path) + TarConstants.EOF_BLOCK; + } + + private static long tarSize(File dir) { + long size = 0; + + if (dir.isFile()) { + return entrySize(dir.length()); + } else { + File[] subFiles = dir.listFiles(); + + if (subFiles != null && subFiles.length > 0) { + for (File file : subFiles) { + if (file.isFile()) { + size += entrySize(file.length()); + } else { + size += tarSize(file); + } + } + } else { + // Empty folder header + return TarConstants.HEADER_BLOCK; + } + } + + return size; + } + + private static long entrySize(long fileSize) { + long size = 0; + size += TarConstants.HEADER_BLOCK; // Header + size += fileSize; // File size + + long extra = size % TarConstants.DATA_BLOCK; + + if (extra > 0) { + size += (TarConstants.DATA_BLOCK - extra); // pad + } + + return size; + } + + public static String trim(String s, char c) { + StringBuffer tmp = new StringBuffer(s); + for (int i = 0; i < tmp.length(); i++) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + for (int i = tmp.length() - 1; i >= 0; i--) { + if (tmp.charAt(i) != c) { + break; + } else { + tmp.deleteCharAt(i); + } + } + + return tmp.toString(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java new file mode 100644 index 0000000..58a1c5e --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java @@ -0,0 +1,19 @@ +package org.kivy.android; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class GenericBroadcastReceiver extends BroadcastReceiver { + + GenericBroadcastReceiverCallback listener; + + public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) { + super(); + this.listener = listener; + } + + public void onReceive(Context context, Intent intent) { + this.listener.onReceive(context, intent); + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java new file mode 100644 index 0000000..1a87c98 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java @@ -0,0 +1,8 @@ +package org.kivy.android; + +import android.content.Intent; +import android.content.Context; + +public interface GenericBroadcastReceiverCallback { + void onReceive(Context context, Intent intent); +}; diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java new file mode 100644 index 0000000..6a0c4d3 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java @@ -0,0 +1,480 @@ + +package org.kivy.android; + +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +import android.view.ViewGroup; +import android.view.SurfaceView; +import android.app.Activity; +import android.content.Intent; +import android.util.Log; +import android.widget.Toast; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.PowerManager; +import android.graphics.PixelFormat; +import android.view.SurfaceHolder; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; +import android.content.Intent; +import android.widget.ImageView; +import java.io.InputStream; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; + +import org.libsdl.app.SDLActivity; + +import org.kivy.android.PythonUtil; +import org.kivy.android.launcher.Project; + +import org.renpy.android.ResourceManager; +import org.renpy.android.AssetExtract; + + +public class PythonActivity extends SDLActivity { + private static final String TAG = "PythonActivity"; + + public static PythonActivity mActivity = null; + + private ResourceManager resourceManager = null; + private Bundle mMetaData = null; + private PowerManager.WakeLock mWakeLock = null; + + public String getAppRoot() { + String app_root = getFilesDir().getAbsolutePath() + "/app"; + return app_root; + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "My oncreate running"); + resourceManager = new ResourceManager(this); + + Log.v(TAG, "About to do super onCreate"); + super.onCreate(savedInstanceState); + Log.v(TAG, "Did super onCreate"); + + this.mActivity = this; + this.showLoadingScreen(); + + new UnpackFilesTask().execute(getAppRoot()); + } + + public void loadLibraries() { + String app_root = new String(getAppRoot()); + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file); + } + + public void recursiveDelete(File f) { + if (f.isDirectory()) { + for (File r : f.listFiles()) { + recursiveDelete(r); + } + } + f.delete(); + } + + /** + * Show an error using a toast. (Only makes sense from non-UI + * threads.) + */ + public void toastError(final String msg) { + + final Activity thisActivity = this; + + runOnUiThread(new Runnable () { + public void run() { + Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show(); + } + }); + + // Wait to show the error. + synchronized (this) { + try { + this.wait(1000); + } catch (InterruptedException e) { + } + } + } + + private class UnpackFilesTask extends AsyncTask { + @Override + protected String doInBackground(String... params) { + File app_root_file = new File(params[0]); + Log.v(TAG, "Ready to unpack"); + unpackData("private", app_root_file); + return null; + } + + @Override + protected void onPostExecute(String result) { + // Figure out the directory where the game is. If the game was + // given to us via an intent, then we use the scheme-specific + // part of that intent to determine the file to launch. We + // also use the android.txt file to determine the orientation. + // + // Otherwise, we use the public data, if we have it, or the + // private data if we do not. + mActivity.finishLoad(); + + // finishLoad called setContentView with the SDL view, which + // removed the loading screen. However, we still need it to + // show until the app is ready to render, so pop it back up + // on top of the SDL view. + mActivity.showLoadingScreen(); + + String app_root_dir = getAppRoot(); + if (getIntent() != null && getIntent().getAction() != null && + getIntent().getAction().equals("org.kivy.LAUNCH")) { + File path = new File(getIntent().getData().getSchemeSpecificPart()); + + Project p = Project.scanDirectory(path); + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", p.dir + "/main.py"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", p.dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", p.dir); + + if (p != null) { + if (p.landscape) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + // Let old apps know they started. + try { + FileWriter f = new FileWriter(new File(path, ".launch")); + f.write("started"); + f.close(); + } catch (IOException e) { + // pass + } + } else { + SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo"); + SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir); + SDLActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir); + } + + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); + Log.v(TAG, "Setting env vars for start.c and Python to use"); + SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); + SDLActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir); + SDLActivity.nativeSetEnv("PYTHONHOME", app_root_dir); + SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib"); + SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2"); + + try { + Log.v(TAG, "Access to our meta-data..."); + mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo( + mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); + if ( mActivity.mMetaData.getInt("wakelock") == 1 ) { + mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + mActivity.mWakeLock.acquire(); + } + if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) { + Log.v(TAG, "Surface will be transparent."); + getSurface().setZOrderOnTop(true); + getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + Log.i(TAG, "Surface will NOT be transparent"); + } + } catch (PackageManager.NameNotFoundException e) { + } + } + + @Override + protected void onPreExecute() { + } + + @Override + protected void onProgressUpdate(Void... values) { + } + } + + public void unpackData(final String resource, File target) { + + Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName()); + + // The version of data in memory and on disk. + String data_version = resourceManager.getString(resource + "_version"); + String disk_version = null; + + Log.v(TAG, "Data version is " + data_version); + + // If no version, no unpacking is necessary. + if (data_version == null) { + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String disk_version_fn = filesDir + "/" + resource + ".version"; + + try { + byte buf[] = new byte[64]; + InputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + disk_version = ""; + } + + // If the disk data is out of date, extract it and write the + // version file. + // if (! data_version.equals(disk_version)) { + if (! data_version.equals(disk_version)) { + Log.v(TAG, "Extracting " + resource + " assets."); + + recursiveDelete(target); + target.mkdirs(); + + AssetExtract ae = new AssetExtract(this); + if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { + toastError("Could not extract " + resource + " data."); + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(disk_version_fn); + os.write(data_version.getBytes()); + os.close(); + } catch (Exception e) { + Log.w("python", e); + } + } + } + + public static ViewGroup getLayout() { + return mLayout; + } + + public static SurfaceView getSurface() { + return mSurface; + } + + //---------------------------------------------------------------------------- + // Listener interface for onNewIntent + // + + public interface NewIntentListener { + void onNewIntent(Intent intent); + } + + private List newIntentListeners = null; + + public void registerNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + this.newIntentListeners = Collections.synchronizedList(new ArrayList()); + this.newIntentListeners.add(listener); + } + + public void unregisterNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + return; + this.newIntentListeners.remove(listener); + } + + @Override + protected void onNewIntent(Intent intent) { + if ( this.newIntentListeners == null ) + return; + this.onResume(); + synchronized ( this.newIntentListeners ) { + Iterator iterator = this.newIntentListeners.iterator(); + while ( iterator.hasNext() ) { + (iterator.next()).onNewIntent(intent); + } + } + } + + //---------------------------------------------------------------------------- + // Listener interface for onActivityResult + // + + public interface ActivityResultListener { + void onActivityResult(int requestCode, int resultCode, Intent data); + } + + private List activityResultListeners = null; + + public void registerActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + this.activityResultListeners = Collections.synchronizedList(new ArrayList()); + this.activityResultListeners.add(listener); + } + + public void unregisterActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + return; + this.activityResultListeners.remove(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if ( this.activityResultListeners == null ) + return; + this.onResume(); + synchronized ( this.activityResultListeners ) { + Iterator iterator = this.activityResultListeners.iterator(); + while ( iterator.hasNext() ) + (iterator.next()).onActivityResult(requestCode, resultCode, intent); + } + } + + public static void start_service(String serviceTitle, String serviceDescription, + String pythonServiceArgument) { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath(); + String filesDirectory = argument; + String app_root_dir = PythonActivity.mActivity.getAppRoot(); + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", app_root_dir); + serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo"); + serviceIntent.putExtra("pythonHome", app_root_dir); + serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib"); + serviceIntent.putExtra("serviceTitle", serviceTitle); + serviceIntent.putExtra("serviceDescription", serviceDescription); + serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument); + PythonActivity.mActivity.startService(serviceIntent); + } + + public static void stop_service() { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + PythonActivity.mActivity.stopService(serviceIntent); + } + + /** Loading screen implementation + * keepActive() is a method plugged in pollInputDevices in SDLActivity. + * Once it's called twice, the loading screen will be removed. + * The first call happen as soon as the window is created, but no image has been + * displayed first. My tests showed that we can wait one more. This might delay + * the real available of few hundred milliseconds. + * The real deal is to know if a rendering has already happen. The previous + * python-for-android and kivy was having something for that, but this new version + * is not compatible, and would require a new kivy version. + * In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called. + */ + public static ImageView mImageView = null; + int mLoadingCount = 2; + + @Override + public void keepActive() { + if (this.mLoadingCount > 0) { + this.mLoadingCount -= 1; + if (this.mLoadingCount == 0) { + this.removeLoadingScreen(); + } + } + } + + public void removeLoadingScreen() { + runOnUiThread(new Runnable() { + public void run() { + if (PythonActivity.mImageView != null && + PythonActivity.mImageView.getParent() != null) { + ((ViewGroup)PythonActivity.mImageView.getParent()).removeView( + PythonActivity.mImageView); + PythonActivity.mImageView = null; + } + } + }); + } + + + protected void showLoadingScreen() { + // load the bitmap + // 1. if the image is valid and we don't have layout yet, assign this bitmap + // as main view. + // 2. if we have a layout, just set it in the layout. + // 3. If we have an mImageView already, then do nothing because it will have + // already been made the content view or added to the layout. + + if (mImageView == null) { + int presplashId = this.resourceManager.getIdentifier("presplash", "drawable"); + InputStream is = this.getResources().openRawResource(presplashId); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(is); + } finally { + try { + is.close(); + } catch (IOException e) {}; + } + + mImageView = new ImageView(this); + mImageView.setImageBitmap(bitmap); + + /* + * Set the presplash loading screen background color + * https://developer.android.com/reference/android/graphics/Color.html + * Parse the color string, and return the corresponding color-int. + * If the string cannot be parsed, throws an IllegalArgumentException exception. + * Supported formats are: #RRGGBB #AARRGGBB or one of the following names: + * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', + * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia', + * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'. + */ + String backgroundColor = resourceManager.getString("presplash_color"); + if (backgroundColor != null) { + try { + mImageView.setBackgroundColor(Color.parseColor(backgroundColor)); + } catch (IllegalArgumentException e) {} + } + mImageView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + + } + + if (mLayout == null) { + setContentView(mImageView); + } else if (PythonActivity.mImageView.getParent() == null){ + mLayout.addView(mImageView); + } + + } + + @Override + protected void onPause() { + // fooabc + if ( this.mWakeLock != null && mWakeLock.isHeld()){ + this.mWakeLock.release(); + } + + Log.v(TAG, "onPause()"); + super.onPause(); + } + + @Override + protected void onResume() { + if ( this.mWakeLock != null){ + this.mWakeLock.acquire(); + } + Log.v(TAG, "onResume()"); + super.onResume(); + } + + + +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java new file mode 100644 index 0000000..046dc18 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java @@ -0,0 +1,153 @@ +package org.kivy.android; + +import android.os.Build; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import android.app.Service; +import android.os.IBinder; +import android.os.Bundle; +import android.content.Intent; +import android.content.Context; +import android.util.Log; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Process; +import java.io.File; + +import org.kivy.android.PythonUtil; + +import org.renpy.android.Hardware; + + +public class PythonService extends Service implements Runnable { + + // Thread for Python code + private Thread pythonThread = null; + + // Python environment variables + private String androidPrivate; + private String androidArgument; + private String pythonName; + private String pythonHome; + private String pythonPath; + private String serviceEntrypoint; + // Argument to pass to Python code, + private String pythonServiceArgument; + public static PythonService mService = null; + private Intent startIntent = null; + + private boolean autoRestartService = false; + + public void setAutoRestartService(boolean restart) { + autoRestartService = restart; + } + + public boolean canDisplayNotification() { + return true; + } + + public int startType() { + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (pythonThread != null) { + Log.v("python service", "service exists, do not start again"); + return START_NOT_STICKY; + } + + startIntent = intent; + Bundle extras = intent.getExtras(); + androidPrivate = extras.getString("androidPrivate"); + androidArgument = extras.getString("androidArgument"); + serviceEntrypoint = extras.getString("serviceEntrypoint"); + pythonName = extras.getString("pythonName"); + pythonHome = extras.getString("pythonHome"); + pythonPath = extras.getString("pythonPath"); + pythonServiceArgument = extras.getString("pythonServiceArgument"); + + pythonThread = new Thread(this); + pythonThread.start(); + + if (canDisplayNotification()) { + doStartForeground(extras); + } + + return startType(); + } + + protected void doStartForeground(Bundle extras) { + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + Notification notification; + Context context = getApplicationContext(); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + notification = new Notification( + context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis()); + try { + // prevent using NotificationCompat, this saves 100kb on apk + Method func = notification.getClass().getMethod( + "setLatestEventInfo", Context.class, CharSequence.class, + CharSequence.class, PendingIntent.class); + func.invoke(notification, context, serviceTitle, serviceDescription, pIntent); + } catch (NoSuchMethodException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + } + } else { + Notification.Builder builder = new Notification.Builder(context); + builder.setContentTitle(serviceTitle); + builder.setContentText(serviceDescription); + builder.setContentIntent(pIntent); + builder.setSmallIcon(context.getApplicationInfo().icon); + notification = builder.build(); + } + startForeground(1, notification); + } + + @Override + public void onDestroy() { + super.onDestroy(); + pythonThread = null; + if (autoRestartService && startIntent != null) { + Log.v("python service", "service restart requested"); + startService(startIntent); + } + Process.killProcess(Process.myPid()); + } + + @Override + public void run(){ + String app_root = getFilesDir().getAbsolutePath() + "/app"; + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file); + this.mService = this; + nativeStart( + androidPrivate, androidArgument, + serviceEntrypoint, pythonName, + pythonHome, pythonPath, + pythonServiceArgument); + stopSelf(); + } + + // Native part + public static native void nativeStart( + String androidPrivate, String androidArgument, + String serviceEntrypoint, String pythonName, + String pythonHome, String pythonPath, + String pythonServiceArgument); +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java new file mode 100644 index 0000000..6574dae --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java @@ -0,0 +1,97 @@ +package org.kivy.android; + +import java.io.File; + +import android.util.Log; +import java.util.ArrayList; +import java.io.FilenameFilter; +import java.util.regex.Pattern; + +public class PythonUtil { + private static final String TAG = "pythonutil"; + + protected static void addLibraryIfExists(ArrayList libsList, String pattern, File libsDir) { + // pattern should be the name of the lib file, without the + // preceding "lib" or suffix ".so", for instance "ssl.*" will + // match files of the form "libssl.*.so". + File [] files = libsDir.listFiles(); + + pattern = "lib" + pattern + "\\.so"; + Pattern p = Pattern.compile(pattern); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + String name = file.getName(); + Log.v(TAG, "Checking pattern " + pattern + " against " + name); + if (p.matcher(name).matches()) { + Log.v(TAG, "Pattern " + pattern + " matched file " + name); + libsList.add(name.substring(3, name.length() - 3)); + } + } + } + + protected static ArrayList getLibraries(File filesDir) { + + String libsDirPath = filesDir.getParentFile().getParentFile().getAbsolutePath() + "/lib/"; + File libsDir = new File(libsDirPath); + + ArrayList libsList = new ArrayList(); + addLibraryIfExists(libsList, "crystax", libsDir); + addLibraryIfExists(libsList, "sqlite3", libsDir); + libsList.add("SDL2"); + libsList.add("SDL2_image"); + libsList.add("SDL2_mixer"); + libsList.add("SDL2_ttf"); + addLibraryIfExists(libsList, "ssl.*", libsDir); + addLibraryIfExists(libsList, "crypto.*", libsDir); + libsList.add("python2.7"); + libsList.add("python3.5m"); + libsList.add("main"); + return libsList; + } + + public static void loadLibraries(File filesDir) { + + String filesDirPath = filesDir.getAbsolutePath(); + boolean foundPython = false; + + for (String lib : getLibraries(filesDir)) { + Log.v(TAG, "Loading library: " + lib); + try { + System.loadLibrary(lib); + if (lib.startsWith("python")) { + foundPython = true; + } + } catch(UnsatisfiedLinkError e) { + // If this is the last possible libpython + // load, and it has failed, give a more + // general error + Log.v(TAG, "Library loading error: " + e.getMessage()); + if (lib.startsWith("python3.6") && !foundPython) { + throw new java.lang.RuntimeException("Could not load any libpythonXXX.so"); + } else if (lib.startsWith("python")) { + continue; + } else { + Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib); + throw e; + } + } + } + + try { + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so"); + System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so"); + } catch(UnsatisfiedLinkError e) { + Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay."); + } + + try { + // System.loadLibrary("ctypes"); + System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so"); + } catch(UnsatisfiedLinkError e) { + Log.v(TAG, "Unsatisfied linker when loading ctypes"); + } + + Log.v(TAG, "Loaded everything!"); + } +} + diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java new file mode 100644 index 0000000..9911356 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java @@ -0,0 +1,45 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonEvent { + private final Lock lock = new ReentrantLock(); + private final Condition cond = lock.newCondition(); + private boolean flag = false; + + public void set() { + lock.lock(); + try { + flag = true; + cond.signalAll(); + } finally { + lock.unlock(); + } + } + + public void wait_() throws InterruptedException { + lock.lock(); + try { + while (!flag) { + cond.await(); + } + } finally { + lock.unlock(); + } + } + + public void clear() { + lock.lock(); + try { + flag = false; + cond.signalAll(); + } finally { + lock.unlock(); + } + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java new file mode 100644 index 0000000..22f9d90 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java @@ -0,0 +1,19 @@ +package org.kivy.android.concurrency; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ryan on 3/28/14. + */ +public class PythonLock { + private final Lock lock = new ReentrantLock(); + + public void acquire() { + lock.lock(); + } + + public void release() { + lock.unlock(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java new file mode 100644 index 0000000..9177b43 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java @@ -0,0 +1,99 @@ +package org.kivy.android.launcher; + +import java.io.UnsupportedEncodingException; +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +import android.util.Log; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + + +/** + * This represents a project we've scanned for. + */ +public class Project { + + public String dir = null; + String title = null; + String author = null; + Bitmap icon = null; + public boolean landscape = false; + + static String decode(String s) { + try { + return new String(s.getBytes("ISO-8859-1"), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return s; + } + } + + /** + * Scans directory for a android.txt file. If it finds one, + * and it looks valid enough, then it creates a new Project, + * and returns that. Otherwise, returns null. + */ + public static Project scanDirectory(File dir) { + + // We might have a link file. + if (dir.getAbsolutePath().endsWith(".link")) { + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + String directory = p.getProperty("directory", null); + + if (directory == null) { + return null; + } + + dir = new File(directory); + + } catch (Exception e) { + Log.i("Project", "Couldn't open link file " + dir, e); + } + } + + // Make sure we're dealing with a directory. + if (! dir.isDirectory()) { + return null; + } + + try { + + // Scan the android.txt file. + File propfile = new File(dir, "android.txt"); + FileInputStream in = new FileInputStream(propfile); + Properties p = new Properties(); + p.load(in); + in.close(); + + // Get the various properties. + String title = decode(p.getProperty("title", "Untitled")); + String author = decode(p.getProperty("author", "")); + boolean landscape = p.getProperty("orientation", "portrait").equals("landscape"); + + // Create the project object. + Project rv = new Project(); + rv.title = title; + rv.author = author; + rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath()); + rv.landscape = landscape; + rv.dir = dir.getAbsolutePath(); + + return rv; + + } catch (Exception e) { + Log.i("Project", "Couldn't open android.txt", e); + } + + return null; + + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java new file mode 100644 index 0000000..f66debf --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java @@ -0,0 +1,44 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.view.Gravity; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.ImageView; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import org.renpy.android.ResourceManager; + +public class ProjectAdapter extends ArrayAdapter { + + private Activity mContext; + private ResourceManager resourceManager; + + public ProjectAdapter(Activity context) { + super(context, 0); + + mContext = context; + resourceManager = new ResourceManager(context); + } + + public View getView(int position, View convertView, ViewGroup parent) { + Project p = getItem(position); + + View v = resourceManager.inflateView("chooser_item"); + TextView title = (TextView) resourceManager.getViewById(v, "title"); + TextView author = (TextView) resourceManager.getViewById(v, "author"); + ImageView icon = (ImageView) resourceManager.getViewById(v, "icon"); + + title.setText(p.title); + author.setText(p.author); + icon.setImageBitmap(p.icon); + + return v; + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java new file mode 100644 index 0000000..17eec32 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java @@ -0,0 +1,94 @@ +package org.kivy.android.launcher; + +import android.app.Activity; +import android.os.Bundle; + +import android.content.Intent; +import android.content.res.Resources; +import android.util.Log; +import android.view.View; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView; +import android.os.Environment; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import android.net.Uri; + +import org.renpy.android.ResourceManager; + +public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener { + + ResourceManager resourceManager; + + String urlScheme; + + @Override + public void onStart() + { + super.onStart(); + + resourceManager = new ResourceManager(this); + + urlScheme = resourceManager.getString("urlScheme"); + + // Set the window title. + setTitle(resourceManager.getString("appName")); + + // Scan the sdcard for files, and sort them. + File dir = new File(Environment.getExternalStorageDirectory(), urlScheme); + + File entries[] = dir.listFiles(); + + if (entries == null) { + entries = new File[0]; + } + + Arrays.sort(entries); + + // Create a ProjectAdapter and fill it with projects. + ProjectAdapter projectAdapter = new ProjectAdapter(this); + + // Populate it with the properties files. + for (File d : entries) { + Project p = Project.scanDirectory(d); + if (p != null) { + projectAdapter.add(p); + } + } + + if (projectAdapter.getCount() != 0) { + + View v = resourceManager.inflateView("project_chooser"); + ListView l = (ListView) resourceManager.getViewById(v, "projectList"); + + l.setAdapter(projectAdapter); + l.setOnItemClickListener(this); + + setContentView(v); + + } else { + + View v = resourceManager.inflateView("project_empty"); + TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText"); + + emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit."); + + setContentView(v); + } + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + Project p = (Project) parent.getItemAtPosition(position); + + Intent intent = new Intent( + "org.kivy.LAUNCH", + Uri.fromParts(urlScheme, p.dir, "")); + + intent.setClassName(getPackageName(), "org.kivy.android.PythonActivity"); + this.startActivity(intent); + this.finish(); + } +} diff --git a/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java new file mode 100644 index 0000000..e1dc084 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java @@ -0,0 +1,1579 @@ +package org.libsdl.app; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.lang.reflect.Method; + +import android.app.*; +import android.content.*; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.os.*; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.media.*; +import android.hardware.*; + +/** + SDL Activity +*/ +public class SDLActivity extends Activity { + private static final String TAG = "SDL"; + + // Keep track of the paused state + public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mExitCalledFromJava; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static View mTextEdit; + protected static ViewGroup mLayout; + protected static SDLJoystickHandler mJoystickHandler; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + // Audio + protected static AudioTrack mAudioTrack; + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } + } + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mSingleton = null; + mSurface = null; + mTextEdit = null; + mLayout = null; + mJoystickHandler = null; + mSDLThread = null; + mAudioTrack = null; + mExitCalledFromJava = false; + mBrokenLibraries = false; + mIsPaused = false; + mIsSurfaceReady = false; + mHasFocus = true; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v("SDL", "Device: " + android.os.Build.DEVICE); + Log.v("SDL", "Model: " + android.os.Build.MODEL); + Log.v("SDL", "onCreate():" + mSingleton); + super.onCreate(savedInstanceState); + + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + } + + // We don't do this in onCreate because we unpack and load the app data on a thread + // and we can't run setup tasks until that thread completes. + protected void finishLoad() { + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up the surface + mSurface = new SDLSurface(getApplication()); + + if(Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } + else { + mJoystickHandler = new SDLJoystickHandler(); + } + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mSurface); + + setContentView(mLayout); + } + + // Events + @Override + protected void onPause() { + Log.v("SDL", "onPause()"); + super.onPause(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handlePause(); + } + + @Override + protected void onResume() { + Log.v("SDL", "onResume()"); + super.onResume(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleResume(); + } + + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.mHasFocus = hasFocus; + if (hasFocus) { + SDLActivity.handleResume(); + } + } + + @Override + public void onLowMemory() { + Log.v("SDL", "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v("SDL", "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + // Send a quit message to the application + SDLActivity.mExitCalledFromJava = true; + SDLActivity.nativeQuit(); + + // Now wait for the SDL thread to quit + if (SDLActivity.mSDLThread != null) { + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v("SDL", "Problem stopping thread: " + e); + } + SDLActivity.mSDLThread = null; + + //Log.v("SDL", "Finished waiting for SDL thread"); + } + + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + + // Completely closes application. + System.exit(0); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + + int keyCode = event.getKeyCode(); + // Ignore certain special keys so they're handled by Android + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_CAMERA || + keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ + keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed + * is the first to be called, mIsSurfaceReady should still be set + * to 'true' during the call to onPause (in a usual scenario). + */ + public static void handlePause() { + if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { + SDLActivity.mIsPaused = true; + SDLActivity.nativePause(); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + } + + /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. + * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume + * every time we get one of those events, only if it comes after surfaceDestroyed + */ + public static void handleResume() { + if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { + SDLActivity.mIsPaused = false; + SDLActivity.nativeResume(); + mSurface.handleResume(); + } + } + + /* The native thread has finished */ + public static void handleNativeExit() { + SDLActivity.mSDLThread = null; + mSingleton.finish(); + } + + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_UNUSED = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + protected boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + protected static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Context context = getContext(); + if (context == null) { + Log.e(TAG, "error handling message, getContext() returned null"); + return; + } + switch (msg.arg1) { + case COMMAND_CHANGE_TITLE: + if (context instanceof Activity) { + ((Activity) context).setTitle((String)msg.obj); + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + mTextEdit.setVisibility(View.GONE); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + break; + } + default: + if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { + Log.e(TAG, "error handling message, command is " + msg.arg1); + } + } + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + return commandHandler.sendMessage(msg); + } + + // C functions we call + public static native int nativeInit(Object arguments); + public static native void nativeLowMemory(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void onNativeResize(int x, int y, int format, float rate); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + public static native void nativeSetEnv(String j_name, String j_value); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native void nativeFlipBuffers(); + public static native int nativeAddJoystick(int device_id, String name, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); + + /** + * This method is called by SDL using JNI. + */ + public static void flipBuffers() { + SDLActivity.nativeFlipBuffers(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + // Called from SDLMain() thread and can't directly affect the view + return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return mSingleton; + } + + /** + * This method is called by SDL using JNI. + * @return result of getSystemService(name) but executed on UI thread. + */ + public Object getSystemServiceFromUiThread(final String name) { + final Object lock = new Object(); + final Object[] results = new Object[2]; // array for writable variables + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + results[0] = getSystemService(name); + results[1] = Boolean.TRUE; + lock.notify(); + } + } + }); + if (results[1] == null) { + try { + lock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + return results[0]; + } + + static class ShowTextInputTask implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputTask(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + @Override + public void run() { + AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( + w, h + HEIGHT_PADDING, x, y); + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + } + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return SDLActivity.mSurface.getNativeSurface(); + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e("SDL", "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioQuit() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack = null; + } + } + + // Input + + /** + * This method is called by SDL using JNI. + * @return an array which may be empty but is never null. + */ + public static int[] inputGetInputDeviceIds(int sources) { + int[] ids = InputDevice.getDeviceIds(); + int[] filtered = new int[ids.length]; + int used = 0; + for (int i = 0; i < ids.length; ++i) { + InputDevice device = InputDevice.getDevice(ids[i]); + if ((device != null) && ((device.getSources() & sources) != 0)) { + filtered[used++] = device.getId(); + } + } + return Arrays.copyOf(filtered, used); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + if (SDLActivity.mSDLThread != null) { + mJoystickHandler.pollInputDevices(); + SDLActivity.mSingleton.keepActive(); + } + } + + /** + * Trick needed for loading screen + */ + public void keepActive() { + } + + // APK extension files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + */ + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION")); + Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION")); + + try { + // To avoid direct dependency on Google APK extension library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, this, mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + ex.printStackTrace(); + fileStream = null; + } + + if (fileStream == null) { + throw new IOException(); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray