From 40b7833bada565386e30c44b7ba53136659c1c1a Mon Sep 17 00:00:00 2001 From: Mis012 Date: Fri, 18 Jul 2025 20:06:17 +0200 Subject: [PATCH] WIP --- .gdb_history | 1 + meson.build | 4 +- src/api-impl-jni/android_view_Window.c | 5 + .../generated_headers/android_view_View.h | 8 + .../android_view_ViewTreeObserver.h | 21 +++ .../generated_headers/android_view_Window.h | 8 + src/api-impl-jni/handle_cache.c | 3 + src/api-impl-jni/handle_cache.h | 4 + .../android_location_LocationManager.c | 2 + src/api-impl-jni/views/AndroidLayout.c | 8 +- src/api-impl-jni/views/android_view_View.c | 25 ++- .../views/android_view_ViewTreeObserver.c | 38 +++++ src/api-impl-jni/widgets/WrapperWidget.c | 27 +++- src/api-impl/android/app/Activity.java | 2 +- src/api-impl/android/app/Dialog.java | 2 +- src/api-impl/android/app/Instrumentation.java | 4 +- src/api-impl/android/view/View.java | 93 ++++++++--- src/api-impl/android/view/ViewGroup.java | 2 + .../android/view/ViewTreeObserver.java | 22 ++- src/api-impl/android/view/Window.java | 19 ++- src/api-impl/android/widget/FrameLayout.java | 2 + src/api-impl/android/widget/LinearLayout.java | 4 + src/main-executable/inspector_page.c | 151 ++++++++++++++++++ src/main-executable/inspector_page.h | 20 +++ src/main-executable/main.c | 11 +- 25 files changed, 438 insertions(+), 48 deletions(-) create mode 100644 .gdb_history create mode 100644 src/api-impl-jni/generated_headers/android_view_ViewTreeObserver.h create mode 100644 src/api-impl-jni/views/android_view_ViewTreeObserver.c create mode 100644 src/main-executable/inspector_page.c create mode 100644 src/main-executable/inspector_page.h diff --git a/.gdb_history b/.gdb_history new file mode 100644 index 00000000..4286f428 --- /dev/null +++ b/.gdb_history @@ -0,0 +1 @@ +r diff --git a/meson.build b/meson.build index 8f525118..ca74fa4f 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('android_translation_layer', ['c', 'java'], default_options: ['b_lundef=false']) +project('android_translation_layer', ['c', 'java'], default_options: ['c_std=gnu23', 'b_lundef=false']) gnome = import('gnome') fs = import('fs') @@ -145,6 +145,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', @@ -197,6 +198,7 @@ executable('android-translation-layer', [ 'src/main-executable/actions.c', 'src/main-executable/back_button.c', 'src/main-executable/bionic_compat.c', + 'src/main-executable/inspector_page.c', 'src/main-executable/libc_bio_path_overrides.c', resources ], 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 08632e9d..73d0a175 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 073bc436..6d8fde66 100644 --- a/src/api-impl-jni/handle_cache.c +++ b/src/api-impl-jni/handle_cache.c @@ -135,4 +135,7 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.uri.class = _REF((*env)->FindClass(env, "android/net/Uri")); handle_cache.uri.parse = _STATIC_METHOD(handle_cache.uri.class, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + + 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 462e3fb4..549e9465 100644 --- a/src/api-impl-jni/handle_cache.h +++ b/src/api-impl-jni/handle_cache.h @@ -145,6 +145,10 @@ struct handle_cache { jclass class; jmethodID parse; } uri; + struct { + jclass class; + jmethodID dispatchOnGlobalLayout; + } view_tree_observer; }; extern struct handle_cache handle_cache; diff --git a/src/api-impl-jni/location/android_location_LocationManager.c b/src/api-impl-jni/location/android_location_LocationManager.c index f19e38fa..f4c4a41c 100644 --- a/src/api-impl-jni/location/android_location_LocationManager.c +++ b/src/api-impl-jni/location/android_location_LocationManager.c @@ -19,6 +19,7 @@ static void location_updated ( JNIEnv *env; (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6); jclass class = (*env)->FindClass(env, "android/location/LocationManager"); + printf("XXLOCXX: latitude, longitude, heading: %lf %lf %lf\n", latitude, longitude, heading); jlong timestamp = timestamp_s * 1000 + timestamp_ms; (*env)->CallStaticVoidMethod(env, class, _STATIC_METHOD(class, "locationUpdated", "(DDDDDDJ)V"), latitude, longitude, altitude, accuracy, speed, heading, timestamp); } @@ -38,5 +39,6 @@ JNIEXPORT void JNICALL Java_android_location_LocationManager_nativeGetLocation(J g_signal_connect(portal, "location-updated", G_CALLBACK(location_updated), jvm); } + printf("XXLOCXX: Java_android_location_LocationManager_nativeGetLocation\n"); xdp_portal_location_monitor_start (portal, NULL, 0, 0, XDP_LOCATION_ACCURACY_EXACT, XDP_LOCATION_MONITOR_FLAG_NONE, NULL, NULL, NULL); } diff --git a/src/api-impl-jni/views/AndroidLayout.c b/src/api-impl-jni/views/AndroidLayout.c index 3ab9afc8..86ee146b 100644 --- a/src/api-impl-jni/views/AndroidLayout.c +++ b/src/api-impl-jni/views/AndroidLayout.c @@ -18,6 +18,8 @@ static int make_measure_spec(int layout_size, int for_size) return -1; } +extern int snapshot_in_progress; + static void android_layout_measure(GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { int widthMeasureSpec = 0; @@ -25,12 +27,16 @@ static void android_layout_measure(GtkLayoutManager *layout_manager, GtkWidget * AndroidLayout *layout = ATL_ANDROID_LAYOUT(layout_manager); JNIEnv *env = get_jni_env(); + /* if we're inside a shanpshot, this must be getting called purely to make Gtk call gtk_widget_clear_resize_queued */ + if(snapshot_in_progress) + return; + // If the parent widget is also an AndroidLayout, the measurement will already have happened in Java if ((layout->width || layout->height) && !ATL_IS_ANDROID_LAYOUT(gtk_widget_get_layout_manager(gtk_widget_get_parent(gtk_widget_get_parent(widget))))) { widthMeasureSpec = make_measure_spec(layout->width, orientation == GTK_ORIENTATION_VERTICAL ? for_size : -1); heightMeasureSpec = make_measure_spec(layout->height, orientation == GTK_ORIENTATION_HORIZONTAL ? for_size : -1); - // if layout params say match_parent, but GTK doesnt specify the dimension, fallback to old specification if available + // if layout params say match_parent, but GTK doesn't specify the dimension, fall back to old specification if available if (widthMeasureSpec == -1) widthMeasureSpec = _GET_INT_FIELD(layout->view, "oldWidthMeasureSpec"); if (heightMeasureSpec == -1) diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index 71145d7e..ca0b7af6 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -307,8 +307,14 @@ 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->layout_width == -1 && wrapper->layout_height == -1) + printf("[%p] in _setOnTouchListener: [%d, %d]\n", wrapper->child, wrapper->real_width, wrapper->real_height); + + 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); } @@ -409,8 +415,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; } @@ -582,9 +586,10 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1measure(JNIEnv *env, jobje width = width_spec_size; } + printf("[%p] in Java_android_view_View_native_1measure, calling setMeasuredDimension with [%d,%d]\n", _PTR(widget_ptr), width, height); (*env)->CallVoidMethod(env, this, handle_cache.view.setMeasuredDimension, width, height); } - +void _gdb_force_java_stack_trace(void); JNIEXPORT void JNICALL Java_android_view_View_native_1layout(JNIEnv *env, jobject this, jlong widget_ptr, jint l, jint t, jint r, jint b) { GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))); @@ -605,6 +610,10 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1layout(JNIEnv *env, jobjec allocation.width = width; allocation.height = height; } + if(width == 540 && height == 0) { + printf("[%p] in Java_android_view_View_native_1layout, calling gtk_widget_size_allocate with [%d,%d]\n", _PTR(widget_ptr), width, height); + //_gdb_force_java_stack_trace(); + } gtk_widget_size_allocate(widget, &allocation, -1); } @@ -841,3 +850,9 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1keep_1screen_1on(JNIEnv *e g_object_set_data(G_OBJECT(widget), "keep-screen-on-cookie", GINT_TO_POINTER(cookie)); } } + +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..63d913e0 --- /dev/null +++ b/src/api-impl-jni/views/android_view_ViewTreeObserver.c @@ -0,0 +1,38 @@ +#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); + } +} + +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) { + /* FIXME: calling this on unrooted widgets may be valid */ + fprintf(stderr, "in Java_android_view_ViewTreeObserver_native_1set_1have_1global_1layout_1listeners: no window\n"); + return; + } + + GdkFrameClock *clock = gtk_widget_get_frame_clock(window); + + if(have_listeners) { + /* this adds our callback before the existing handler, which means we effectively execute before the paint phase (after layout) */ + gulong 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 { + gulong signal_handle = _GET_LONG_FIELD(this, "onGlobalLayout_signal_handle"); + if (signal_handle) + g_signal_handler_disconnect(G_OBJECT(clock), signal_handle); + } +} diff --git a/src/api-impl-jni/widgets/WrapperWidget.c b/src/api-impl-jni/widgets/WrapperWidget.c index 00adb9b8..8849d301 100644 --- a/src/api-impl-jni/widgets/WrapperWidget.c +++ b/src/api-impl-jni/widgets/WrapperWidget.c @@ -10,7 +10,7 @@ G_DEFINE_TYPE(WrapperWidget, wrapper_widget, GTK_TYPE_WIDGET) -typedef enum { ATL_ID = 1, ATL_ID_NAME, ATL_CLASS_NAME, ATL_SUPER_CLASS_NAMES, N_PROPERTIES } WrapperWidgetProperty; +typedef enum { ATL_ID = 1, ATL_ID_NAME, ATL_CLASS_NAME, ATL_SUPER_CLASS_NAMES, ATL_REAL_WIDTH, ATL_REAL_HEIGHT, ATL_HAS_CUSTOM_DRAW, N_PROPERTIES } WrapperWidgetProperty; static GParamSpec *wrapper_widget_properties[N_PROPERTIES] = { NULL, }; static void wrapper_widget_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) @@ -30,6 +30,10 @@ static void wrapper_widget_get_property(GObject *object, guint property_id, GVal JNIEnv *env = get_jni_env(); jobject jobj = self->jobj; + if(!jobj) { /* FIXME */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + return; + } jclass class = _CLASS(jobj); switch ((WrapperWidgetProperty) property_id) @@ -85,6 +89,24 @@ static void wrapper_widget_get_property(GObject *object, guint property_id, GVal } + case ATL_REAL_WIDTH: + { + g_value_set_int(value, self->real_width); + break; + } + + case ATL_REAL_HEIGHT: + { + g_value_set_int(value, self->real_height); + break; + } + + case ATL_HAS_CUSTOM_DRAW: + { + g_value_set_boolean(value, self->draw_method != NULL); + break; + } + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -232,6 +254,9 @@ static void wrapper_widget_class_init(WrapperWidgetClass *class) wrapper_widget_properties[ATL_ID_NAME] = g_param_spec_string("ATL-id-name", "ATL: ID name", "Name of the ID of the component", "", G_PARAM_READABLE); wrapper_widget_properties[ATL_CLASS_NAME] = g_param_spec_string("ATL-class-name", "ATL: Class name", "Name of the class of the component", "", G_PARAM_READABLE); wrapper_widget_properties[ATL_SUPER_CLASS_NAMES] = g_param_spec_string("ATL-superclasses-names", "ATL: Super classes names", "Names of all the superclasses of the component class", "", G_PARAM_READABLE); + wrapper_widget_properties[ATL_REAL_WIDTH] = g_param_spec_int("ATL-real-width", "ATL: real width", "Real width of the widget", 0, INT32_MAX, 0, G_PARAM_READABLE); + wrapper_widget_properties[ATL_REAL_HEIGHT] = g_param_spec_int("ATL-real-height", "ATL: real height", "Real height of the widget", 0, INT32_MAX, 0, G_PARAM_READABLE); + wrapper_widget_properties[ATL_HAS_CUSTOM_DRAW] = g_param_spec_boolean("ATL-has-custom-draw", "ATL: has custom draw", "Indicates if the widget implements it's own drawing", FALSE, G_PARAM_READABLE); g_object_class_install_properties (object_class, N_PROPERTIES, wrapper_widget_properties); } diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index ca1afaae..2d0147c5 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 732744fd..75102537 100644 --- a/src/api-impl/android/app/Dialog.java +++ b/src/api-impl/android/app/Dialog.java @@ -31,7 +31,7 @@ public class Dialog implements Window.Callback, DialogInterface { this.context = context; nativePtr = nativeInit(); window = new Window(context, this); - 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 65e539be..59b4daf4 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -828,15 +828,18 @@ 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; - private int left; - private int top; - private int right; - private int bottom; + private int left = 0; + private int top = 0; + private int right = 0; + private int bottom = 0; + + private float translationX = 0; + private float translationY = 0; private int scrollX = 0; private int scrollY = 0; @@ -1067,13 +1070,23 @@ public class View implements Drawable.Callback { native_setLayoutParams(widget, params.width, params.height, gravity, params.weight, leftMargin, topMargin, rightMargin, bottomMargin); layout_params = params; + + System.out.printf("[0x%x] [%s] in setLayoutParams [%d, %d]\n", this.widget, getIdName(), params.width, params.height); + if(params.width == -1 && params.height == 0) + try {throw new Exception("stack trace");} catch(Exception e) {e.printStackTrace();} + } public ViewGroup.LayoutParams getLayoutParams() { + if(layout_params != null) + System.out.printf("[0x%x] [%s] in getLayoutParams [%d, %d]\n", this.widget, getIdName(), layout_params.width, layout_params.height); return layout_params; } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { + if("net.osmand.plus:id/list".equals(getIdName())) + try { throw new Exception("stack trace"); } catch (Exception e) { e.printStackTrace(); } + System.out.printf("[0x%x] [%s] in setMeasuredDimension [%d, %d]\n", this.widget, getIdName(), measuredWidth, measuredHeight); this.measuredWidth = measuredWidth; this.measuredHeight = measuredHeight; } @@ -1199,8 +1212,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 { + /* FIXME: better handling of unrooted widgets? */ + return new ViewTreeObserver(null); + } } protected void onFinishInflate() {} @@ -1464,10 +1486,12 @@ public class View implements Drawable.Callback { } public final int getMeasuredWidth() { + System.out.printf("[0x%x] [%s] in getMeasuredWidth width: %d\n", this.widget, getIdName(), this.measuredWidth); return this.measuredWidth & MEASURED_SIZE_MASK; } public final int getMeasuredHeight() { + System.out.printf("[0x%x] [%s] in getMeasuredHeight height: %d\n", this.widget, getIdName(), this.measuredHeight); return this.measuredHeight & MEASURED_SIZE_MASK; } @@ -1659,13 +1683,31 @@ public class View implements Drawable.Callback { return viewPropertyAnimator; } - public float getTranslationX() {return 0.f;} - public float getTranslationY() {return 0.f;} - public void setTranslationX(float translationX) {} + public float getTranslationX() { + return translationX; + } + + public float getTranslationY() { + return translationY; + } + + /* this is probably slower than on AOSP, becuse it's supposed to bypass the layout phase + * and just schedule redraw (which we technically could do, but it wouldn't change the hitbox, + * which it's supposed to) */ + public void setTranslationX(float translationX) { + this.translationX = translationX; + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)getLayoutParams(); + lp.leftMargin += translationX; + setLayoutParams(lp); + requestLayout(); + } + public void setTranslationY(float translationY) { - // CoordinatorLayout abuses this method to trigger a layout pass - if (getClass().getName().equals("androidx.coordinatorlayout.widget.CoordinatorLayout")) - native_queueAllocate(widget); + this.translationY = translationY; + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)getLayoutParams(); + lp.topMargin += translationY; + setLayoutParams(lp); + requestLayout(); } public void setX(float x) { @@ -1915,7 +1957,7 @@ public class View implements Drawable.Callback { keepScreenOn = screenOn; } - protected void onAttachedToWindow () { + protected void onAttachedToWindow() { attachedToWindow = true; if (onAttachStateChangeListener != null) { onAttachStateChangeListener.onViewAttachedToWindow(this); @@ -1972,12 +2014,16 @@ public class View implements Drawable.Callback { } public void forceLayout() { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - requestLayout(); - } - }); + if(Looper.myLooper() == Looper.getMainLooper()) { + requestLayout(); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + requestLayout(); + } + }); + } } private OnAttachStateChangeListener onAttachStateChangeListener; @@ -2103,8 +2149,6 @@ public class View implements Drawable.Callback { public int getTextAlignment() {return 0;} - public float getY() {return 0.f;} - public View findViewWithTag(Object tag) { if (Objects.equals(tag, this.tag)) return this; @@ -2198,7 +2242,12 @@ public class View implements Drawable.Callback { public boolean isDirty() { return false; } - public float getX() { return getLeft(); } + public float getX() { + return getLeft(); + } + public float getY() { + return getTop(); + } public boolean getGlobalVisibleRect(Rect visibleRect, Point globalOffset) { boolean result = native_getGlobalVisibleRect(widget, visibleRect); diff --git a/src/api-impl/android/view/ViewGroup.java b/src/api-impl/android/view/ViewGroup.java index 2fed5106..d8af5de8 100644 --- a/src/api-impl/android/view/ViewGroup.java +++ b/src/api-impl/android/view/ViewGroup.java @@ -247,6 +247,7 @@ public class ViewGroup extends View implements ViewParent, ViewManager { } public static int getChildMeasureSpec(int spec, int padding, int childDimension) { + System.out.printf("[static] in getChildMeasureSpec(%d, %d, %d)\n", spec, padding, childDimension); int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); @@ -307,6 +308,7 @@ public class ViewGroup extends View implements ViewParent, ViewManager { break; } // noinspection ResourceType + System.out.printf("[static] in getChildMeasureSpec, calling MeasureSpec.makeMeasureSpec(%d, %d)\n", resultSize, resultMode); return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } diff --git a/src/api-impl/android/view/ViewTreeObserver.java b/src/api-impl/android/view/ViewTreeObserver.java index a81b5b34..89d22a12 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; + + 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; } /** @@ -493,18 +497,19 @@ public final class ViewTreeObserver { if (mOnGlobalLayoutListeners == null) { mOnGlobalLayoutListeners = new CopyOnWriteArray(); + 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() { + /*new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { listener.onGlobalLayout(); } - }); + });*/ } @@ -539,6 +544,9 @@ public final class ViewTreeObserver { return; } mOnGlobalLayoutListeners.remove(victim); + + if(mOnGlobalLayoutListeners.size() == 0) + native_set_have_global_layout_listeners(false); } /** @@ -1038,4 +1046,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 dcb17ed5..5dbf1555 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(); @@ -42,6 +44,11 @@ public class Window { decorView.onAttachedToWindow(); } + 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) {} @@ -65,12 +72,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()); } @@ -160,4 +161,10 @@ public class Window { public void setReturnTransition(Transition transition) {} public void setEnterTransition(Transition transition) {} + + 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); } diff --git a/src/api-impl/android/widget/FrameLayout.java b/src/api-impl/android/widget/FrameLayout.java index 01b55c0b..516c4289 100644 --- a/src/api-impl/android/widget/FrameLayout.java +++ b/src/api-impl/android/widget/FrameLayout.java @@ -196,6 +196,7 @@ public class FrameLayout extends ViewGroup { } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { + System.out.printf("[0x%x] in layoutChildren %d %d %d %d\n", this.widget, left, top, right, bottom); final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); @@ -244,6 +245,7 @@ public class FrameLayout extends ViewGroup { default: childTop = parentTop + lp.topMargin; } + System.out.printf("[0x%x] [%s] in layoutChildren calling child[0x%x].layout*(%d, %d, %d, %d)\n", this.widget, getIdName(), child.widget, childLeft, childTop, width, height); child.layout(childLeft, childTop, childLeft + width, childTop + height); } } diff --git a/src/api-impl/android/widget/LinearLayout.java b/src/api-impl/android/widget/LinearLayout.java index d02cad91..cda3418c 100644 --- a/src/api-impl/android/widget/LinearLayout.java +++ b/src/api-impl/android/widget/LinearLayout.java @@ -830,8 +830,12 @@ public class LinearLayout extends ViewGroup { mTotalLength += paddingTop + paddingBottom; int heightSize = mTotalLength; // Check against our minimum height + System.out.printf("[0x%x] in measureVetrical calling Math.max(%d, %d)\n", this.widget, heightSize, getSuggestedMinimumHeight()); heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec + System.out.printf("[0x%x] in measureVetrical calling resolveSizeAndState(%d, %d, %d)\n", this.widget, heightSize, heightMeasureSpec, 0); + if (heightMeasureSpec == 0x80000000) + try { throw new Exception("stack trace"); } catch(Exception e) {e.printStackTrace();} int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or diff --git a/src/main-executable/inspector_page.c b/src/main-executable/inspector_page.c new file mode 100644 index 00000000..1cbb2b7d --- /dev/null +++ b/src/main-executable/inspector_page.c @@ -0,0 +1,151 @@ +#include + +#include "../api-impl-jni/widgets/WrapperWidget.h" + +#include "inspector_page.h" + +G_DEFINE_FINAL_TYPE(ATLInspectorPage, atl_inspector_page, GTK_TYPE_BOX) + +typedef enum { PROP_TITLE = 1, N_PROPERTIES } ATLInspectorPageProperty; + +static GParamSpec *props[N_PROPERTIES]; + +static void atl_inspector_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +{ + switch ((ATLInspectorPageProperty)property_id) + { + case PROP_TITLE: + { + g_value_set_string(value, "ATL"); + break; + } + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/* HACK: private - no guarantee that the inspector window will always have the object tree pointer at this offset (or that the object tree widget will continue to be a thing) */ +typedef struct { + GtkWindow parent; + + GtkWidget *top_stack; + GtkWidget *object_stack; + GtkWidget *button_stack; + GtkWidget *object_tree; + /* ... */ +} GtkInspectorWindow; + +typedef bool (*widget_callback)(GtkWidget *widget, ATLInspectorPage *atl_inspector_page, const char *id_text); + +static bool _traverse_widget_hierarchy(GtkWidget *widget, widget_callback func, ATLInspectorPage *atl_inspector_page, const char *id_text) +{ + func(widget, atl_inspector_page, id_text); + + GtkWidget *child = gtk_widget_get_first_child(widget); + while (child) + { + if(_traverse_widget_hierarchy(child, func, atl_inspector_page, id_text)) + return true; + child = gtk_widget_get_next_sibling(child); + } + + return false; +} + +void for_each_rooted_widget(GtkApplication *application, widget_callback func, ATLInspectorPage *atl_inspector_page, const char *id_text) +{ + GList *windows_head = gtk_application_get_windows(application); + + for (GList *l = windows_head; l; l = l->next) + { + GtkWindow *window = GTK_WINDOW(l->data); + + if(_traverse_widget_hierarchy(GTK_WIDGET(window), func, atl_inspector_page, id_text)) + break; + } +} + +void show_details(ATLInspectorPage *atl_inspector_page, GObject *gobject) +{ + GtkInspectorWindow *inspector_window = (GtkInspectorWindow *)gtk_widget_get_root(GTK_WIDGET(atl_inspector_page)); + + GObject *object_tree = G_OBJECT(inspector_window->object_tree); + /* HACK: private - no guarantee that the object tree will always have these signals (or that the object tree widget will continue to be a thing) */ + g_signal_emit_by_name(object_tree, "object-selected", gobject); + g_signal_emit_by_name(object_tree, "object-activated", gobject); +} + +gboolean show_gobject_details(ATLInspectorPage *atl_inspector_page, GtkEntry *entry) +{ + const char *gobject_ptr_text = gtk_entry_buffer_get_text(gtk_entry_get_buffer(GTK_ENTRY(entry))); + uintptr_t gobject_ptr = (uintptr_t)strtoul(gobject_ptr_text, NULL, 16); + + show_details(atl_inspector_page, G_OBJECT(gobject_ptr)); + + return false; +} + +bool show_details_if_id(GtkWidget *widget, ATLInspectorPage *atl_inspector_page, const char *id_text) { + if(g_type_is_a(G_OBJECT_TYPE(widget), wrapper_widget_get_type())) { + char *id = NULL; + g_object_get(widget, "ATL-id-name", &id, NULL); + if(id && !strcmp(id, id_text)) { + show_details(atl_inspector_page, G_OBJECT(widget)); + return true; + } + } + + return false; +} + +extern GtkWindow *window; +gboolean show_id_details(ATLInspectorPage *atl_inspector_page, GtkEntry *entry) +{ + const char *id_text = gtk_entry_buffer_get_text(gtk_entry_get_buffer(GTK_ENTRY(entry))); + + GtkApplication *application = gtk_window_get_application(window); + for_each_rooted_widget(application, show_details_if_id, atl_inspector_page, id_text); + + return false; +} + +static void atl_inspector_page_init (ATLInspectorPage *atl_inspector_page) +{ + gtk_orientable_set_orientation(GTK_ORIENTABLE(atl_inspector_page), GTK_ORIENTATION_VERTICAL); + + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + GtkWidget *entry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "GObject *"); + GtkWidget *button = gtk_button_new_with_label("show details"); + g_signal_connect(button, "clicked", G_CALLBACK(show_gobject_details), entry); + + gtk_box_append(GTK_BOX(box), entry); + gtk_box_append(GTK_BOX(box), button); + + gtk_box_append(GTK_BOX(atl_inspector_page), box); + + GtkWidget *id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + GtkWidget *id_entry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(id_entry), "android:id"); + GtkWidget *id_button = gtk_button_new_with_label("show details"); + g_signal_connect(id_button, "clicked", G_CALLBACK(show_id_details), id_entry); + + gtk_box_append(GTK_BOX(id_box), id_entry); + gtk_box_append(GTK_BOX(id_box), id_button); + + gtk_box_append(GTK_BOX(atl_inspector_page), box); + gtk_box_append(GTK_BOX(atl_inspector_page), id_box); +} + +static void atl_inspector_page_class_init(ATLInspectorPageClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->get_property = atl_inspector_get_property; + + props[PROP_TITLE] = g_param_spec_string("title", NULL, NULL, "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, N_PROPERTIES, props); +} diff --git a/src/main-executable/inspector_page.h b/src/main-executable/inspector_page.h new file mode 100644 index 00000000..3750502d --- /dev/null +++ b/src/main-executable/inspector_page.h @@ -0,0 +1,20 @@ +#ifndef INSPECTOR_PAGE_H +#define INSPECTOR_PAGE_H + +#include + +struct _ATLInspectorPage +{ + GtkBox parent_instance; +}; + +struct _ATLInspectorPageClass +{ + GtkWidgetClass parent_class; +}; + +#define ATL_INSPECTOR_PAGE_TYPE (atl_inspector_page_get_type()) + +G_DECLARE_FINAL_TYPE (ATLInspectorPage, atl_inspector_page, ATL, INSPECTOR_PAGE, GtkBox) + +#endif diff --git a/src/main-executable/main.c b/src/main-executable/main.c index d7cfbfe8..13ff338a 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -10,9 +10,10 @@ #include "../api-impl-jni/util.h" #include "../api-impl-jni/app/android_app_Activity.h" +#include "actions.h" #include "back_button.h" #include "libc_bio_path_overrides.h" -#include "actions.h" +#include "inspector_page.h" #include #include @@ -486,6 +487,9 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h /* -- misc -- */ + if (g_io_extension_point_lookup ("gtk-inspector-page")) + g_io_extension_point_implement ("gtk-inspector-page", ATL_INSPECTOR_PAGE_TYPE, "android-translation-layer", 10); + window = gtk_application_window_new(app); const char *disable_decoration_env = getenv("ATL_DISABLE_WINDOW_DECORATIONS"); @@ -528,7 +532,10 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); - (*env)->CallVoidMethod(env, application_object, _METHOD(handle_cache.application.class, "onCreate", "()V")); + jmethodID on_create_method = _METHOD(handle_cache.application.class, "onCreate", "()V"); + if ((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + (*env)->CallVoidMethod(env, application_object, on_create_method); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env);