From 04d6dcd895120934cf3d5867259e9ef44beeca4e Mon Sep 17 00:00:00 2001 From: Ryan Pessa Date: Tue, 12 Jan 2016 10:27:55 -0600 Subject: [PATCH 1/2] enable services and other features in sdl2 bootstrap --- .../bootstraps/sdl2/build/build.py | 26 ++++- .../bootstraps/sdl2/build/jni/src/start.c | 56 +++++------ .../src/org/kivy/android/PythonActivity.java | 89 +++++++---------- .../src/org/kivy/android/PythonService.java | 97 +++++++++++++++++++ .../src/org/kivy/android/PythonUtil.java | 38 ++++++++ .../kivy/android/concurrency/PythonEvent.java | 45 +++++++++ .../kivy/android/concurrency/PythonLock.java | 19 ++++ .../src/org/renpy/android/PythonService.java | 12 +++ .../build/templates/AndroidManifest.tmpl.xml | 40 +++++++- 9 files changed, 336 insertions(+), 86 deletions(-) create mode 100644 pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonService.java create mode 100644 pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonUtil.java create mode 100644 pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonEvent.java create mode 100644 pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonLock.java create mode 100644 pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/PythonService.java diff --git a/pythonforandroid/bootstraps/sdl2/build/build.py b/pythonforandroid/bootstraps/sdl2/build/build.py index 9249491d51..0262bf5138 100755 --- a/pythonforandroid/bootstraps/sdl2/build/build.py +++ b/pythonforandroid/bootstraps/sdl2/build/build.py @@ -221,7 +221,7 @@ def make_package(args): os.unlink('assets/private.mp3') # In order to speedup import and initial depack, - # construct a python27.zip if not using CrystaX's pre-zipped package + # construct a python27.zip make_python_zip() # Package up the private and public data. @@ -281,10 +281,16 @@ def make_package(args): with open(args.intent_filters) as fd: args.intent_filters = fd.read() + service = False + service_main = join(realpath(args.private), 'service', 'main.py') + if os.path.exists(service_main) or os.path.exists(service_main + 'o'): + service = True + render( 'AndroidManifest.tmpl.xml', 'AndroidManifest.xml', args=args, + service=service, ) render( @@ -311,6 +317,7 @@ def make_package(args): def parse_args(args=None): global BLACKLIST_PATTERNS, WHITELIST_PATTERNS + default_android_api = 12 import argparse ap = argparse.ArgumentParser(description='''\ Package a Python application for Android. @@ -367,18 +374,35 @@ def parse_args(args=None): help=('Add a Java .jar to the libs, so you can access its ' 'classes with pyjnius. You can specify this ' 'argument more than once to include multiple jars')) + ap.add_argument('--sdk', dest='sdk_version', default=-1, + type=int, help=('Android SDK version to use. Default to ' + 'the value of minsdk')) + ap.add_argument('--minsdk', dest='min_sdk_version', + default=default_android_api, type=int, + help=('Minimum Android SDK version to use. Default to ' + 'the value of ANDROIDAPI, or {} if not set' + .format(default_android_api))) ap.add_argument('--intent-filters', dest='intent_filters', help=('Add intent-filters xml rules to the ' 'AndroidManifest.xml file. The argument is a ' 'filename containing xml. The filename should be ' 'located relative to the python-for-android ' 'directory')) + ap.add_argument('--with-billing', dest='billing_pubkey', + help='If set, the billing service will be added (not implemented)') if args is None: args = sys.argv[1:] args = ap.parse_args(args) args.ignore_path = [] + if args.billing_pubkey: + print('Billing not yet supported in sdl2 bootstrap!') + exit(1) + + if args.sdk_version == -1: + args.sdk_version = args.min_sdk_version + if args.permissions is None: args.permissions = [] diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c b/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c index 40ba1ab3b1..d7f0b1951c 100644 --- a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c +++ b/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c @@ -122,7 +122,7 @@ int main(int argc, char *argv[]) { LOG("Initialize Python for Android"); /* env_argument = "/data/data/org.kivy.android/files"; */ env_argument = getenv("ANDROID_ARGUMENT"); - /* setenv("ANDROID_APP_PATH", env_argument, 1); */ + setenv("ANDROID_APP_PATH", env_argument, 1); /* setenv("ANDROID_ARGUMENT", env_argument, 1); */ /* setenv("ANDROID_PRIVATE", env_argument, 1); */ @@ -314,32 +314,32 @@ int main(int argc, char *argv[]) { return ret; } -/* JNIEXPORT void JNICALL JAVA_EXPORT_NAME(PythonService_nativeStart) ( JNIEnv* env, jobject thiz, */ -/* jstring j_android_private, */ -/* jstring j_android_argument, */ -/* jstring j_python_home, */ -/* jstring j_python_path, */ -/* jstring j_arg ) */ -/* { */ -/* jboolean iscopy; */ -/* const char *android_private = (*env)->GetStringUTFChars(env, j_android_private, &iscopy); */ -/* const char *android_argument = (*env)->GetStringUTFChars(env, j_android_argument, &iscopy); */ -/* const char *python_home = (*env)->GetStringUTFChars(env, j_python_home, &iscopy); */ -/* const char *python_path = (*env)->GetStringUTFChars(env, j_python_path, &iscopy); */ -/* const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy); */ - -/* setenv("ANDROID_PRIVATE", android_private, 1); */ -/* setenv("ANDROID_ARGUMENT", android_argument, 1); */ -/* setenv("PYTHONOPTIMIZE", "2", 1); */ -/* setenv("PYTHONHOME", python_home, 1); */ -/* setenv("PYTHONPATH", python_path, 1); */ -/* setenv("PYTHON_SERVICE_ARGUMENT", arg, 1); */ - -/* char *argv[] = { "service" }; */ -/* /\* ANDROID_ARGUMENT points to service subdir, */ -/* * so main() will run main.py from this dir */ -/* *\/ */ -/* main(1, argv); */ -/* } */ +JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart ( JNIEnv* env, jobject thiz, + jstring j_android_private, + jstring j_android_argument, + jstring j_python_home, + jstring j_python_path, + jstring j_arg ) +{ + jboolean iscopy; + const char *android_private = (*env)->GetStringUTFChars(env, j_android_private, &iscopy); + const char *android_argument = (*env)->GetStringUTFChars(env, j_android_argument, &iscopy); + const char *python_home = (*env)->GetStringUTFChars(env, j_python_home, &iscopy); + const char *python_path = (*env)->GetStringUTFChars(env, j_python_path, &iscopy); + const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy); + + setenv("ANDROID_PRIVATE", android_private, 1); + setenv("ANDROID_ARGUMENT", android_argument, 1); + setenv("PYTHONOPTIMIZE", "2", 1); + setenv("PYTHONHOME", python_home, 1); + setenv("PYTHONPATH", python_path, 1); + setenv("PYTHON_SERVICE_ARGUMENT", arg, 1); + + char *argv[] = { "service" }; + /* ANDROID_ARGUMENT points to service subdir, + * so main() will run main.py from this dir + */ + main(1, argv); +} #endif diff --git a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java index 68359345cb..e519971d3a 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java @@ -24,9 +24,12 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ApplicationInfo; +import android.content.Intent; import org.libsdl.app.SDLActivity; +import org.kivy.android.PythonUtil; + import org.renpy.android.ResourceManager; import org.renpy.android.AssetExtract; @@ -35,7 +38,7 @@ 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; @@ -51,9 +54,9 @@ protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "About to do super onCreate"); super.onCreate(savedInstanceState); Log.v(TAG, "Did super onCreate"); - + this.mActivity = this; - + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); Log.v(TAG, "Setting env vars for start.c and Python to use"); SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory); @@ -62,7 +65,7 @@ protected void onCreate(Bundle savedInstanceState) { SDLActivity.nativeSetEnv("PYTHONHOME", mFilesDirectory); SDLActivity.nativeSetEnv("PYTHONPATH", mFilesDirectory + ":" + mFilesDirectory + "/lib"); - + // nativeSetEnv("ANDROID_ARGUMENT", getFilesDir()); try { @@ -84,54 +87,11 @@ protected void onCreate(Bundle savedInstanceState) { } catch (PackageManager.NameNotFoundException e) { } } - - // This is just overrides the normal SDLActivity, which just loads - // SDL2 and main - protected String[] getLibraries() { - return new String[] { - "SDL2", - "SDL2_image", - "SDL2_mixer", - "SDL2_ttf", - "main" - }; - } - + public void loadLibraries() { - // AND: This should probably be replaced by a call to super - for (String lib : getLibraries()) { - System.loadLibrary(lib); - } - - try { - System.loadLibrary("python2.7"); - } catch(UnsatisfiedLinkError e) { - Log.v(TAG, "Failed to load libpython2.7"); - } - - try { - System.loadLibrary("python3.5m"); - } catch(UnsatisfiedLinkError e) { - Log.v(TAG, "Failed to load libpython3.5m"); - } - - try { - System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_io.so"); - System.load(getFilesDir() + "/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(getFilesDir() + "/lib/python2.7/lib-dynload/_ctypes.so"); - } catch(UnsatisfiedLinkError e) { - Log.v(TAG, "Unsatisfied linker when loading ctypes"); - } - - Log.v(TAG, "Loaded everything!"); + PythonUtil.loadLibraries(getFilesDir()); } - + public void recursiveDelete(File f) { if (f.isDirectory()) { for (File r : f.listFiles()) { @@ -163,15 +123,15 @@ public void run() { } } } - + 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. @@ -220,7 +180,7 @@ public void unpackData(final String resource, File target) { } } } - + public static ViewGroup getLayout() { return mLayout; } @@ -298,4 +258,23 @@ protected void onActivityResult(int requestCode, int resultCode, Intent 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; + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", filesDirectory); + serviceIntent.putExtra("pythonHome", argument); + serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/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); + } } diff --git a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonService.java new file mode 100644 index 0000000000..3539c5e108 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonService.java @@ -0,0 +1,97 @@ +package org.kivy.android; + +import android.app.Service; +import android.os.IBinder; +import android.os.Bundle; +import android.content.Intent; +import android.content.Context; +import android.util.Log; +import android.app.Notification; +import android.app.PendingIntent; +import android.os.Process; + +import 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 pythonHome; + private String pythonPath; + // Argument to pass to Python code, + private String pythonServiceArgument; + public static Service mService = null; + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + //Hardware.context = this; + } + + @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; + } + + Bundle extras = intent.getExtras(); + androidPrivate = extras.getString("androidPrivate"); + // service code is located in service subdir + androidArgument = extras.getString("androidArgument") + "/service"; + pythonHome = extras.getString("pythonHome"); + pythonPath = extras.getString("pythonPath"); + pythonServiceArgument = extras.getString("pythonServiceArgument"); + String serviceTitle = extras.getString("serviceTitle"); + String serviceDescription = extras.getString("serviceDescription"); + + pythonThread = new Thread(this); + pythonThread.start(); + + Context context = getApplicationContext(); + Notification notification = new Notification(context.getApplicationInfo().icon, + serviceTitle, + System.currentTimeMillis()); + Intent contextIntent = new Intent(context, PythonActivity.class); + PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + notification.setLatestEventInfo(context, serviceTitle, serviceDescription, pIntent); + startForeground(1, notification); + + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + pythonThread = null; + Process.killProcess(Process.myPid()); + } + + @Override + public void run(){ + PythonUtil.loadLibraries(getFilesDir()); + + this.mService = this; + nativeStart(androidPrivate, androidArgument, pythonHome, pythonPath, + pythonServiceArgument); + } + + // Native part + public static native void nativeStart(String androidPrivate, String androidArgument, + String pythonHome, String pythonPath, + String pythonServiceArgument); + +} diff --git a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonUtil.java new file mode 100644 index 0000000000..46bcaa1406 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonUtil.java @@ -0,0 +1,38 @@ +package org.kivy.android; + +import java.io.File; + +import android.util.Log; + + +public class PythonUtil { + private static final String TAG = "PythonUtil"; + + protected static String[] getLibraries() { + return new String[] { + "SDL2", + "SDL2_image", + "SDL2_mixer", + "SDL2_ttf", + "python2.7", + "main" + }; + } + + public static void loadLibraries(File filesDir) { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } + + System.load(filesDir + "/lib/python2.7/lib-dynload/_io.so"); + System.load(filesDir + "/lib/python2.7/lib-dynload/unicodedata.so"); + + try { + System.load(filesDir + "/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/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonEvent.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonEvent.java new file mode 100644 index 0000000000..9911356ba0 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/src/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/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonLock.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/concurrency/PythonLock.java new file mode 100644 index 0000000000..22f9d903e1 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/src/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/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/PythonService.java new file mode 100644 index 0000000000..73febed68a --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/PythonService.java @@ -0,0 +1,12 @@ +package org.renpy.android; + +import android.util.Log; + +class PythonService extends org.kivy.android.PythonService { + static { + Log.w("PythonService", "Accessing org.renpy.android.PythonService " + + "is deprecated and will be removed in a " + + "future version. Please switch to " + + "org.kivy.android.PythonService."); + } +} diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml index 8d417a652d..8e8047db35 100644 --- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml @@ -8,8 +8,18 @@ android:versionName="{{ args.version }}" android:installLocation="auto"> + = 9 %} + android:xlargeScreens="true" + {% endif %} + /> + - + @@ -24,6 +34,14 @@ {% endif %} {% endfor %} + {% if args.wakelock %} + + {% endif %} + + {% if args.billing_pubkey %} + + {% endif %} +