You've already forked android_translation_layer
mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-10-27 11:48:10 -07:00
ViewTreeObserver: implement onGlobalLayout properly
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* 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
|
||||
@@ -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
|
||||
|
||||
@@ -153,4 +153,7 @@ void set_up_handle_cache(JNIEnv *env)
|
||||
handle_cache.parcel.constructor = _METHOD(handle_cache.parcel.class, "<init>", "(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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,9 +529,7 @@ 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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
44
src/api-impl-jni/views/android_view_ViewTreeObserver.c
Normal file
44
src/api-impl-jni/views/android_view_ViewTreeObserver.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public class Activity extends ContextThemeWrapper implements Window.Callback, La
|
||||
Class<? extends Activity> cls = Class.forName(className).asSubclass(Activity.class);
|
||||
Constructor<? extends Activity> 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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<? extends Activity> 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() {
|
||||
|
||||
@@ -867,7 +867,7 @@ public class View implements Drawable.Callback {
|
||||
private Context context;
|
||||
private Map<Integer,Object> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OnGlobalLayoutListener>();
|
||||
}
|
||||
|
||||
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();
|
||||
if (mOnGlobalLayoutListeners.size() == 0) {
|
||||
native_set_have_global_layout_listeners(true);
|
||||
}
|
||||
});
|
||||
|
||||
mOnGlobalLayoutListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user