diff --git a/meson.build b/meson.build index 3f3a9127..7d63160f 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/views/AndroidLayout.c', 'src/api-impl-jni/views/android_view_View.c', 'src/api-impl-jni/views/android_view_ViewGroup.c', + 'src/api-impl-jni/views/android_view_ViewTreeObserver.c', 'src/api-impl-jni/views/android_view_WindowManagerImpl.c', 'src/api-impl-jni/widgets/WrapperWidget.c', 'src/api-impl-jni/widgets/android_view_SurfaceView.c', diff --git a/src/api-impl-jni/android_view_Window.c b/src/api-impl-jni/android_view_Window.c index 96252b3a..4532a06e 100644 --- a/src/api-impl-jni/android_view_Window.c +++ b/src/api-impl-jni/android_view_Window.c @@ -53,3 +53,8 @@ JNIEXPORT void JNICALL Java_android_view_Window_set_1layout(JNIEnv *env, jobject if (width > 0 && height > 0) gtk_window_set_default_size(gtk_window, width, height); } + +JNIEXPORT void JNICALL Java_android_view_Window_set_1jobject(JNIEnv *env, jclass this, jlong window, jobject window_jobj) +{ + g_object_set_data(G_OBJECT(window), "jobject", _WEAK_REF(window_jobj)); +} diff --git a/src/api-impl-jni/generated_headers/android_view_View.h b/src/api-impl-jni/generated_headers/android_view_View.h index c40a8801..47dd1905 100644 --- a/src/api-impl-jni/generated_headers/android_view_View.h +++ b/src/api-impl-jni/generated_headers/android_view_View.h @@ -359,6 +359,14 @@ JNIEXPORT void JNICALL Java_android_view_View_nativeRequestFocus JNIEXPORT void JNICALL Java_android_view_View_nativeSetFullscreen (JNIEnv *, jobject, jlong, jboolean); +/* + * Class: android_view_View + * Method: native_get_window + * Signature: (J)Landroid/view/Window; + */ +JNIEXPORT jobject JNICALL Java_android_view_View_native_1get_1window + (JNIEnv *, jobject, jlong); + /* * Class: android_view_View * Method: nativeInvalidate diff --git a/src/api-impl-jni/generated_headers/android_view_ViewTreeObserver.h b/src/api-impl-jni/generated_headers/android_view_ViewTreeObserver.h new file mode 100644 index 00000000..512891d9 --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_view_ViewTreeObserver.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_view_ViewTreeObserver */ + +#ifndef _Included_android_view_ViewTreeObserver +#define _Included_android_view_ViewTreeObserver +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: android_view_ViewTreeObserver + * Method: native_set_have_global_layout_listeners + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_android_view_ViewTreeObserver_native_1set_1have_1global_1layout_1listeners + (JNIEnv *, jobject, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl-jni/generated_headers/android_view_Window.h b/src/api-impl-jni/generated_headers/android_view_Window.h index 40bb02e0..ad456dbf 100644 --- a/src/api-impl-jni/generated_headers/android_view_Window.h +++ b/src/api-impl-jni/generated_headers/android_view_Window.h @@ -43,6 +43,14 @@ JNIEXPORT void JNICALL Java_android_view_Window_take_1input_1queue JNIEXPORT void JNICALL Java_android_view_Window_set_1layout (JNIEnv *, jobject, jlong, jint, jint); +/* + * Class: android_view_Window + * Method: set_jobject + * Signature: (JLandroid/view/Window;)V + */ +JNIEXPORT void JNICALL Java_android_view_Window_set_1jobject + (JNIEnv *, jclass, jlong, jobject); + #ifdef __cplusplus } #endif diff --git a/src/api-impl-jni/handle_cache.c b/src/api-impl-jni/handle_cache.c index 39c7f771..c00c1b6b 100644 --- a/src/api-impl-jni/handle_cache.c +++ b/src/api-impl-jni/handle_cache.c @@ -153,4 +153,7 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.parcel.constructor = _METHOD(handle_cache.parcel.class, "", "(JJ)V"); handle_cache.parcel.writeParcelable = _METHOD(handle_cache.parcel.class, "writeParcelable", "(Landroid/os/Parcelable;I)V"); handle_cache.parcel.readParcelable = _METHOD(handle_cache.parcel.class, "readParcelable", "(Ljava/lang/ClassLoader;)Landroid/os/Parcelable;"); + + handle_cache.view_tree_observer.class = _REF((*env)->FindClass(env, "android/view/ViewTreeObserver")); + handle_cache.view_tree_observer.dispatchOnGlobalLayout = _METHOD(handle_cache.view_tree_observer.class, "dispatchOnGlobalLayout", "()V"); } diff --git a/src/api-impl-jni/handle_cache.h b/src/api-impl-jni/handle_cache.h index 4903af1f..224f4ba5 100644 --- a/src/api-impl-jni/handle_cache.h +++ b/src/api-impl-jni/handle_cache.h @@ -166,6 +166,10 @@ struct handle_cache { jmethodID writeParcelable; jmethodID readParcelable; } parcel; + struct { + jclass class; + jmethodID dispatchOnGlobalLayout; + } view_tree_observer; }; extern struct handle_cache handle_cache; diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index 13c7372b..4cbb6992 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -307,8 +307,10 @@ void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget) gtk_widget_add_controller(widget, controller); g_object_set_data(G_OBJECT(widget), "on_intercept_touch_listener", controller); } - if (!wrapper->needs_allocation && (wrapper->layout_width || wrapper->layout_height)) - gtk_widget_size_allocate(GTK_WIDGET(wrapper), &(GtkAllocation){.x = 0, .y = 0, .width = wrapper->layout_width, .height = wrapper->layout_height}, 0); + + if (!wrapper->needs_allocation && (wrapper->real_width || wrapper->real_height)) + gtk_widget_size_allocate(GTK_WIDGET(wrapper), &(GtkAllocation){.x = 0, .y = 0, .width = wrapper->real_width, .height = wrapper->real_height}, 0); + wrapper->needs_allocation = true; gtk_widget_set_overflow(GTK_WIDGET(wrapper), GTK_OVERFLOW_HIDDEN); } @@ -340,11 +342,6 @@ JNIEXPORT jint JNICALL Java_android_view_View_getWidth(JNIEnv *env, jobject this { GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget"))); -/* FIXME: is this needed in Gtk4? - GtkAllocation alloc; - gtk_widget_get_allocation(widget, &alloc); - printf("widget size is currently %dx%d\n", alloc.width, alloc.height); -*/ if (ATL_IS_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget))) { AndroidLayout *layout = ATL_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget)); return layout->real_width; @@ -409,8 +406,6 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setLayoutParams(JNIEnv *en } if (weight > 0.f) { - printf(":::-: setting weight: %f\n", weight); - hexpand = TRUE; vexpand = TRUE; } @@ -534,10 +529,8 @@ JNIEXPORT jlong JNICALL Java_android_view_View_native_1constructor(JNIEnv *env, gtk_widget_add_controller(widget, controller); } - if (_METHOD(class, "onAttachedToWindow", "()V") != handle_cache.view.onAttachedToWindow) - g_signal_connect(wrapper, "map", G_CALLBACK(java_method_cb), handle_cache.view.onAttachedToWindow); - if (_METHOD(class, "onDetachedFromWindow", "()V") != handle_cache.view.onDetachedFromWindow) - g_signal_connect(wrapper, "unmap", G_CALLBACK(java_method_cb), handle_cache.view.onDetachedFromWindow); + g_signal_connect(wrapper, "map", G_CALLBACK(java_method_cb), handle_cache.view.onAttachedToWindow); + g_signal_connect(wrapper, "unmap", G_CALLBACK(java_method_cb), handle_cache.view.onDetachedFromWindow); return _INTPTR(widget); } @@ -870,8 +863,15 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1keep_1screen_1on(JNIEnv *e } } + JNIEXPORT jboolean JNICALL Java_android_view_View_nativeIsAttachedToWindow(JNIEnv *env, jobject this, jlong widget_ptr) { GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); return gtk_widget_get_mapped(widget); } + +JNIEXPORT jobject JNICALL Java_android_view_View_native_1get_1window(JNIEnv *env, jobject this, jlong widget_ptr) +{ + GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); + return g_object_get_data(G_OBJECT(gtk_widget_get_root(widget)), "jobject"); +} diff --git a/src/api-impl-jni/views/android_view_ViewTreeObserver.c b/src/api-impl-jni/views/android_view_ViewTreeObserver.c new file mode 100644 index 00000000..029c03a7 --- /dev/null +++ b/src/api-impl-jni/views/android_view_ViewTreeObserver.c @@ -0,0 +1,44 @@ +#include + +#include "../defines.h" +#include "../util.h" +#include "../generated_headers/android_view_ViewTreeObserver.h" + +static void on_global_layout_callback(GdkFrameClock *clock, jobject view_tree_observer) +{ + JNIEnv *env = get_jni_env(); + (*env)->CallVoidMethod(env, view_tree_observer, handle_cache.view_tree_observer.dispatchOnGlobalLayout); + if((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +extern GtkWidget *window; + +void _gdb_force_java_stack_trace(void); + +JNIEXPORT void JNICALL Java_android_view_ViewTreeObserver_native_1set_1have_1global_1layout_1listeners(JNIEnv *env, jobject this, jboolean have_listeners) +{ + GtkWidget *window =_PTR(_GET_LONG_FIELD(this, "window")); + + if(!window) { + fprintf(stderr, "Java_android_view_ViewTreeObserver_native_1set_1have_1global_1layout_1listeners: no window\n"); + return; + } + + gulong signal_handle = _GET_LONG_FIELD(this, "onGlobalLayout_signal_handle"); + GdkFrameClock *clock = gtk_widget_get_frame_clock(window); + + if(have_listeners && !signal_handle) { + /* this adds our callback before the existing handler, which means we effectively execute before the paint phase (after layout) */ + signal_handle = g_signal_connect(G_OBJECT(clock), "paint", G_CALLBACK(on_global_layout_callback), _REF(this)); // FIXME: cleanup callback for _UNREF + _SET_LONG_FIELD(this, "onGlobalLayout_signal_handle", signal_handle); + } else if (!have_listeners && signal_handle) { + g_signal_handler_disconnect(G_OBJECT(clock), signal_handle); + _SET_LONG_FIELD(this, "onGlobalLayout_signal_handle", 0); + } else { + fprintf(stderr, "Java_android_view_ViewTreeObserver_native_1set_1have_1global_1layout_1listeners: invalid state: have_listeners: %d, signal_handle: 0x%016w64x\n", have_listeners, signal_handle); + exit(1); + } +} diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index c0b5b25a..7dc08942 100644 --- a/src/api-impl/android/app/Activity.java +++ b/src/api-impl/android/app/Activity.java @@ -66,7 +66,7 @@ public class Activity extends ContextThemeWrapper implements Window.Callback, La Class cls = Class.forName(className).asSubclass(Activity.class); Constructor constructor = cls.getConstructor(); Activity activity = constructor.newInstance(); - activity.window.native_window = native_window; + activity.window.set_native_window(native_window); activity.intent = intent; activity.attachBaseContext(new Context()); activity.setTheme(themeResId); diff --git a/src/api-impl/android/app/Dialog.java b/src/api-impl/android/app/Dialog.java index 4a29e6c4..78f3429f 100644 --- a/src/api-impl/android/app/Dialog.java +++ b/src/api-impl/android/app/Dialog.java @@ -40,7 +40,7 @@ public class Dialog implements Window.Callback, DialogInterface { window.setBackgroundDrawable(background); ta.recycle(); - window.native_window = nativePtr; + window.set_native_window(nativePtr); } public Dialog(Context context) { diff --git a/src/api-impl/android/app/Instrumentation.java b/src/api-impl/android/app/Instrumentation.java index 1cec57cb..63f89e07 100644 --- a/src/api-impl/android/app/Instrumentation.java +++ b/src/api-impl/android/app/Instrumentation.java @@ -139,7 +139,7 @@ public class Instrumentation { Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { Activity activity = (Activity)clazz.newInstance(); - activity.getWindow().native_window = Context.this_application.native_window; + activity.getWindow().set_native_window(Context.this_application.native_window); Slog.i(TAG, "activity.getWindow().native_window >"+activity.getWindow().native_window+"<"); return activity; } @@ -168,7 +168,7 @@ public class Instrumentation { Constructor constructor = cls.getConstructor(); final Activity activity = constructor.newInstance(); activity.intent = intent; - activity.getWindow().native_window = Context.this_application.native_window; + activity.getWindow().set_native_window(Context.this_application.native_window); runOnMainSync(new Runnable() { @Override public void run() { diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index 9a9e1f14..45e4be3a 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -867,7 +867,7 @@ public class View implements Drawable.Callback { private Context context; private Map tags = new HashMap<>(); private Object tag; - int gravity = -1; // fallback gravity for layout childs + int gravity = -1; // fallback gravity for layout children int measuredWidth = 0; int measuredHeight = 0; @@ -880,6 +880,8 @@ public class View implements Drawable.Callback { private int scrollX = 0; private int scrollY = 0; + ViewTreeObserver floating_observer = null; + public long widget; // pointer private int oldWidthMeasureSpec = -1; @@ -1236,8 +1238,17 @@ public class View implements Drawable.Callback { public void setSelected(boolean selected) {} + public native Window native_get_window(long widget); public ViewTreeObserver getViewTreeObserver() { - return new ViewTreeObserver(); + Window window = native_get_window(widget); + if (window != null) { + if (window.view_tree_observer == null) + window.view_tree_observer = new ViewTreeObserver(window); + return window.view_tree_observer; + } else { + floating_observer = new ViewTreeObserver(null); + return floating_observer; + } } protected void onFinishInflate() {} @@ -1961,8 +1972,24 @@ public class View implements Drawable.Callback { keepScreenOn = screenOn; } - protected void onAttachedToWindow() {} - protected void onDetachedFromWindow() {} + protected void onAttachedToWindow() { + if (onAttachStateChangeListener != null) { + onAttachStateChangeListener.onViewAttachedToWindow(this); + } + if (keepScreenOn) + native_keep_screen_on(widget, true); + if(floating_observer != null) { + getViewTreeObserver().merge(floating_observer); + floating_observer = null; + } + } + protected void onDetachedFromWindow() { + if (onAttachStateChangeListener != null) { + onAttachStateChangeListener.onViewDetachedFromWindow(this); + } + if (keepScreenOn) + native_keep_screen_on(widget, false); + } public void setLayerType(int layerType, Paint paint) {} @@ -2274,4 +2301,8 @@ public class View implements Drawable.Callback { public Bitmap getDrawingCache() { return null; } public void announceForAccessibility(CharSequence text) {} + + public WindowInsetsController getWindowInsetsController() { + return native_get_window(widget).getInsetsController(); + } } diff --git a/src/api-impl/android/view/ViewTreeObserver.java b/src/api-impl/android/view/ViewTreeObserver.java index a81b5b34..a9ba41bf 100644 --- a/src/api-impl/android/view/ViewTreeObserver.java +++ b/src/api-impl/android/view/ViewTreeObserver.java @@ -51,6 +51,11 @@ public final class ViewTreeObserver { private boolean mAlive = true; + // accessed from native code + private long onGlobalLayout_signal_handle = 0; + + private long window; + /** * Interface definition for a callback to be invoked when the view hierarchy is * attached to and detached from its window. @@ -297,10 +302,9 @@ public final class ViewTreeObserver { public void onComputeInternalInsets(InternalInsetsInfo inoutInfo); } - /** - * Creates a new ViewTreeObserver. This constructor should not be called - */ - ViewTreeObserver() { + ViewTreeObserver(Window window) { + if(window != null) + this.window = window.native_window; } /** @@ -335,7 +339,10 @@ public final class ViewTreeObserver { } } - if (observer.mOnGlobalLayoutListeners != null) { + if (observer.mOnGlobalLayoutListeners != null && observer.mOnGlobalLayoutListeners.size() > 0) { + if(mOnGlobalLayoutListeners == null || mOnGlobalLayoutListeners.size() == 0) + native_set_have_global_layout_listeners(true); + if (mOnGlobalLayoutListeners != null) { mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); } else { @@ -495,17 +502,11 @@ public final class ViewTreeObserver { mOnGlobalLayoutListeners = new CopyOnWriteArray(); } + if (mOnGlobalLayoutListeners.size() == 0) { + native_set_have_global_layout_listeners(true); + } + mOnGlobalLayoutListeners.add(listener); - - // hack: many Applications wait for the global layout before doing anything - // so we dispatch the event immediately - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - listener.onGlobalLayout(); - } - }); - } /** @@ -539,6 +540,9 @@ public final class ViewTreeObserver { return; } mOnGlobalLayoutListeners.remove(victim); + + if(mOnGlobalLayoutListeners.size() == 0) + native_set_have_global_layout_listeners(false); } /** @@ -754,6 +758,9 @@ public final class ViewTreeObserver { * @hide */ private void kill() { + /* clear any callbacks */ + if (mOnGlobalLayoutListeners != null && mOnGlobalLayoutListeners.size() > 0) + native_set_have_global_layout_listeners(false); mAlive = false; } @@ -1038,4 +1045,6 @@ public final class ViewTreeObserver { getArray().clear(); } } + + private native void native_set_have_global_layout_listeners(boolean have_listeners); } diff --git a/src/api-impl/android/view/Window.java b/src/api-impl/android/view/Window.java index 2d3d9ba3..06bc6ca9 100644 --- a/src/api-impl/android/view/Window.java +++ b/src/api-impl/android/view/Window.java @@ -10,6 +10,8 @@ public class Window { public static final int FEATURE_OPTIONS_PANEL = 0; public static final int FEATURE_NO_TITLE = 1; + public ViewTreeObserver view_tree_observer = null; + public static interface Callback { public void onContentChanged(); @@ -41,6 +43,11 @@ public class Window { decorView.setId(android.R.id.content); } + public void set_native_window(long native_window) { + this.native_window = native_window; + set_jobject(native_window, this); + } + public void addFlags(int flags) {} public void setFlags(int flags, int mask) {} public void clearFlags(int flags) {} @@ -64,12 +71,6 @@ public class Window { return decorView; } - public native void set_widget_as_root(long native_window, long widget); - private native void set_title(long native_window, String title); - - public native void take_input_queue(long native_window, InputQueue.Callback callback, InputQueue queue); - public native void set_layout(long native_window, int width, int height); - public void takeInputQueue(InputQueue.Callback callback) { take_input_queue(native_window, callback, new InputQueue()); } @@ -161,4 +162,16 @@ public class Window { public void setEnterTransition(Transition transition) {} public void setGravity(int gravity) {} + + public void setDecorFitsSystemWindows(boolean fits) {} + + public WindowInsetsController getInsetsController() { + return new InsetsController(); + } + + public native void set_widget_as_root(long native_window, long widget); + private native void set_title(long native_window, String title); + public native void take_input_queue(long native_window, InputQueue.Callback callback, InputQueue queue); + public native void set_layout(long native_window, int width, int height); + private static native void set_jobject(long ptr, Window obj); }