diff --git a/meson.build b/meson.build index 00411567..78c21548 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/android_app_NativeActivity.c', 'src/api-impl-jni/android_opengl_GLES20.c', 'src/api-impl-jni/location/android_location_LocationManager.c', + 'src/api-impl-jni/app/android_app_Activity.c', ] + marshal_files, install: true, install_dir : get_option('libdir') / 'java/dex/android_translation_layer/natives', diff --git a/src/api-impl-jni/app/android_app_Activity.c b/src/api-impl-jni/app/android_app_Activity.c new file mode 100644 index 00000000..f0093402 --- /dev/null +++ b/src/api-impl-jni/app/android_app_Activity.c @@ -0,0 +1,99 @@ +#include + +#include + +#include "../defines.h" +#include "../util.h" +#include "android_app_Activity.h" +#include "../generated_headers/android_app_Activity.h" + +static GList *activity_backlog = NULL; +static jobject activity_current = NULL; + +static void activity_close(JNIEnv *env, jobject activity) { + // in case some exception was left unhandled in native code, print it here so we don't confuse it with an exception thrown by onDestroy + if((*env)->ExceptionCheck(env)) { + fprintf(stderr, "app_exit: seems there was a pending exception... :"); + (*env)->ExceptionDescribe(env); + } + + /* -- run the main activity's onDestroy -- */ + (*env)->CallVoidMethod(env, activity, handle_cache.apk_main_activity.onDestroy, NULL); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); +} + +static void activity_update_current(JNIEnv *env) { + jobject activity_new = activity_backlog ? g_list_first(activity_backlog)->data : NULL; + + if (activity_current != activity_new) { + if (activity_current) { + (*env)->CallVoidMethod(env, activity_current, handle_cache.apk_main_activity.onPause); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + (*env)->CallVoidMethod(env, activity_current, handle_cache.apk_main_activity.onStop); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + } + if (activity_new) { + (*env)->CallVoidMethod(env, activity_new, handle_cache.apk_main_activity.onStart); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + (*env)->CallVoidMethod(env, activity_new, handle_cache.apk_main_activity.onResume); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + } + activity_current = activity_new; + } +} + +void activity_window_ready(void) { + JNIEnv *env = get_jni_env(); + + for (GList *l = activity_backlog; l != NULL; l = l->next) { + (*env)->CallVoidMethod(env, l->data, handle_cache.apk_main_activity.onWindowFocusChanged, true); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + } +} + +void activity_close_all(void) { + GList *activities, *l; + JNIEnv *env = get_jni_env(); + // local backup of the backlog + activities = activity_backlog; + // deactivate all activities + activity_backlog = NULL; + activity_update_current(env); + // destroy all activities + for (l = activities; l != NULL; l = l->next) { + activity_close(env, l->data); + _UNREF(l->data); + } + g_list_free(activities); +} + +void activity_start(JNIEnv *env, jobject activity_object) { + /* -- run the activity's onCreate -- */ + (*env)->CallVoidMethod(env, activity_object, handle_cache.apk_main_activity.onCreate, NULL); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + activity_backlog = g_list_prepend(activity_backlog, _REF(activity_object)); + activity_update_current(env); +} + +JNIEXPORT void JNICALL Java_android_app_Activity_nativeFinish(JNIEnv *env, jobject this, jlong window) { + activity_backlog = g_list_remove(activity_backlog, this); + activity_update_current(env); + activity_close(env, this); + _UNREF(this); + if (activity_backlog == NULL) + gtk_window_close(GTK_WINDOW(_PTR(window))); +} + +JNIEXPORT void JNICALL Java_android_app_Activity_nativeStartActivity(JNIEnv *env, jclass class, jobject activity) { + activity_start(env, activity); +} diff --git a/src/api-impl-jni/app/android_app_Activity.h b/src/api-impl-jni/app/android_app_Activity.h new file mode 100644 index 00000000..dea8f0fa --- /dev/null +++ b/src/api-impl-jni/app/android_app_Activity.h @@ -0,0 +1,5 @@ +#include + +void activity_start(JNIEnv *env, jobject activity_object); +void activity_window_ready(void); +void activity_close_all(void); diff --git a/src/api-impl-jni/generated_headers/android_app_Activity.h b/src/api-impl-jni/generated_headers/android_app_Activity.h new file mode 100644 index 00000000..147130f5 --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_app_Activity.h @@ -0,0 +1,31 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_app_Activity */ + +#ifndef _Included_android_app_Activity +#define _Included_android_app_Activity +#ifdef __cplusplus +extern "C" { +#endif +#undef android_app_Activity_MODE_PRIVATE +#define android_app_Activity_MODE_PRIVATE 0L +/* + * Class: android_app_Activity + * Method: nativeFinish + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_android_app_Activity_nativeFinish + (JNIEnv *, jobject, jlong); + +/* + * Class: android_app_Activity + * Method: nativeStartActivity + * Signature: (Landroid/app/Activity;)V + */ +JNIEXPORT void JNICALL Java_android_app_Activity_nativeStartActivity + (JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 52e34f90..90ed7e5c 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -51,6 +51,8 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.apk_main_activity.onWindowFocusChanged = _METHOD(handle_cache.apk_main_activity.class, "onWindowFocusChanged", "(Z)V"); handle_cache.apk_main_activity.onResume = _METHOD(handle_cache.apk_main_activity.class, "onResume", "()V"); handle_cache.apk_main_activity.onDestroy = _METHOD(handle_cache.apk_main_activity.class, "onDestroy", "()V"); + handle_cache.apk_main_activity.onStop = _METHOD(handle_cache.apk_main_activity.class, "onStop", "()V"); + handle_cache.apk_main_activity.onPause = _METHOD(handle_cache.apk_main_activity.class, "onPause", "()V"); handle_cache.attribute_set.class = _REF((*env)->FindClass(env, "android/util/AttributeSet")); if((*env)->ExceptionCheck(env)) diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index 45eba1dd..cd05f15d 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -8,12 +8,13 @@ struct handle_cache { struct { jclass class; - jobject object; jmethodID onCreate; jmethodID onStart; jmethodID onResume; jmethodID onWindowFocusChanged; jmethodID onDestroy; + jmethodID onStop; + jmethodID onPause; } apk_main_activity; struct { jclass class; @@ -78,6 +79,8 @@ struct handle_cache { extern struct handle_cache handle_cache; +JNIEnv * get_jni_env(void); + const char * attribute_set_get_string(JNIEnv *env, jobject attrs, char *attribute, char *schema); int attribute_set_get_int(JNIEnv *env, jobject attrs, char *attribute, char *schema, int default_value); void set_up_handle_cache(JNIEnv *env); diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index c1ff8fe9..40014773 100644 --- a/src/api-impl/android/app/Activity.java +++ b/src/api-impl/android/app/Activity.java @@ -29,6 +29,7 @@ public class Activity extends Context { LayoutInflater layout_inflater; Window window = new Window(); int requested_orientation = -1 /*ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED*/; // dummy + private Intent intent; /** * Helper function to be called from native code to construct main activity @@ -56,6 +57,7 @@ public class Activity extends Context { public Activity() { layout_inflater = new LayoutInflater(); + intent = new Intent(); } public View root_view; @@ -77,8 +79,7 @@ public class Activity extends Context { } public Intent getIntent() { -// return null; // this is the main activity, and it wasn't opened as a result of someone calling "open with" - return new Intent(); // seems some apps don't consider this nullable... + return intent; } public int getRequestedOrientation() { @@ -223,10 +224,45 @@ public class Activity extends Context { public void startActivityForResult(Intent intent, int requestCode) { System.out.println("startActivityForResult(" + intent + ", " + requestCode + ") called, but we don't currently support multiple activities"); - onActivityResult(requestCode, 0 /*RESULT_CANCELED*/, new Intent()); // RESULT_CANCELED is the only pre-defined return value, so hopefully it works out for us + if (intent.getComponent() != null) { + try { + Class cls = Class.forName(intent.getComponent().getClassName()).asSubclass(Activity.class); + Constructor constructor = cls.getConstructor(); + Activity activity = constructor.newInstance(); + activity.intent = intent; + activity.window = getWindow(); + nativeStartActivity(activity); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + onActivityResult(requestCode, 0 /*RESULT_CANCELED*/, new Intent()); // RESULT_CANCELED is the only pre-defined return value, so hopefully it works out for us + } + } + else { + onActivityResult(requestCode, 0 /*RESULT_CANCELED*/, new Intent()); // RESULT_CANCELED is the only pre-defined return value, so hopefully it works out for us + } + } + + public void startActivity(Intent intent) { + System.out.println("startActivity(" + intent + ") called"); + try { + Class cls = Class.forName(intent.getComponent().getClassName()).asSubclass(Activity.class); + Constructor constructor = cls.getConstructor(); + Activity activity = constructor.newInstance(); + activity.intent = intent; + activity.window = getWindow(); + nativeStartActivity(activity); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + } } public final void showDialog(int id) { System.out.println("Activity.showDialog(" + id + ") called"); } + + public void finish() { + nativeFinish(getWindow().native_window); + } + + private native void nativeFinish(long native_window); + private static native void nativeStartActivity(Activity activity); } diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 72c817a7..d8c56ec7 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -5,6 +5,7 @@ #include "../api-impl-jni/defines.h" #include "../api-impl-jni/util.h" +#include "../api-impl-jni/app/android_app_Activity.h" #include #include @@ -34,17 +35,7 @@ GtkWidget *window; gboolean app_exit(GtkWindow* self, JNIEnv *env) // TODO: do more cleanup? { - // in case some exception was left unhandled in native code, print it here so we don't confuse it with an exception thrown by onDestroy - if((*env)->ExceptionCheck(env)) { - fprintf(stderr, "app_exit: seems there was a pending exception... :"); - (*env)->ExceptionDescribe(env); - } - - /* -- run the main activity's onDestroy -- */ - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onDestroy, NULL); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - + activity_close_all(); return false; } @@ -128,10 +119,7 @@ JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk, cha gboolean hacky_on_window_focus_changed_callback(JNIEnv *env) { if(gtk_widget_get_width(window) != 0) { - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onWindowFocusChanged, true); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - + activity_window_ready(); return G_SOURCE_REMOVE; } @@ -170,6 +158,7 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h int errno_libdir; int errno_localdir; int ret; + jobject activity_object; char *apk_classpath = g_file_get_path(files[0]); char *apk_name = g_file_get_basename(files[0]); @@ -364,26 +353,13 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h prepare_main_looper(env); // construct main Activity - handle_cache.apk_main_activity.object = _REF((*env)->CallStaticObjectMethod(env, handle_cache.apk_main_activity.class, + activity_object = (*env)->CallStaticObjectMethod(env, handle_cache.apk_main_activity.class, _STATIC_METHOD(handle_cache.apk_main_activity.class, "createMainActivity", "(Ljava/lang/String;J)Landroid/app/Activity;"), - _JSTRING(d->apk_main_activity_class), _INTPTR(window))); + _JSTRING(d->apk_main_activity_class), _INTPTR(window)); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); - /* -- run the main activity's onCreate -- */ - - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onCreate, NULL); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - -// TODO: some apps wait for this to actually do stuff - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onStart); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onResume); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); + activity_start(env, activity_object); g_timeout_add(10, G_SOURCE_FUNC(hacky_on_window_focus_changed_callback), env);