From e3c0931714ab8023d4c11b77bede3663b581f51f Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Fri, 26 Jul 2024 21:47:08 +0200 Subject: [PATCH] prevent reference cycles between Java and native objects --- src/api-impl-jni/android_graphics_Bitmap.c | 1 - src/api-impl-jni/android_graphics_Canvas.c | 6 + src/api-impl-jni/defines.h | 2 + .../android_graphics_Canvas.h | 8 + .../generated_headers/android_view_View.h | 24 +-- .../generated_headers/android_widget_Button.h | 8 +- .../android_widget_ImageButton.h | 8 +- .../android_graphics_drawable_Drawable.c | 2 +- src/api-impl-jni/util.c | 1 + src/api-impl-jni/util.h | 1 + src/api-impl-jni/views/AndroidLayout.c | 12 +- src/api-impl-jni/views/android_view_View.c | 144 ++++++++---------- src/api-impl-jni/widgets/WrapperWidget.c | 16 +- src/api-impl-jni/widgets/WrapperWidget.h | 3 +- .../widgets/android_widget_AbsListView.c | 2 +- .../widgets/android_widget_AbsSpinner.c | 2 +- .../widgets/android_widget_Button.c | 31 +--- .../widgets/android_widget_EditText.c | 2 +- .../widgets/android_widget_ImageButton.c | 30 +--- .../widgets/android_widget_TextView.c | 1 + src/api-impl/android/app/Activity.java | 2 + src/api-impl/android/graphics/Bitmap.java | 12 ++ src/api-impl/android/graphics/Canvas.java | 14 ++ src/api-impl/android/media/MediaCodec.java | 14 ++ src/api-impl/android/view/View.java | 45 +++++- src/api-impl/android/view/ViewGroup.java | 12 ++ src/api-impl/android/view/Window.java | 11 +- src/api-impl/android/widget/Button.java | 8 +- src/api-impl/android/widget/ImageButton.java | 6 +- src/api-impl/android/widget/TextView.java | 10 ++ 30 files changed, 257 insertions(+), 181 deletions(-) diff --git a/src/api-impl-jni/android_graphics_Bitmap.c b/src/api-impl-jni/android_graphics_Bitmap.c index 2ec24f9e..9cc17149 100644 --- a/src/api-impl-jni/android_graphics_Bitmap.c +++ b/src/api-impl-jni/android_graphics_Bitmap.c @@ -45,7 +45,6 @@ JNIEXPORT jlong JNICALL Java_android_graphics_Bitmap_native_1bitmap_1from_1path( attach_sk_image(pixbuf); - g_object_ref(pixbuf); return _INTPTR(pixbuf); } /* new empty bitmap */ diff --git a/src/api-impl-jni/android_graphics_Canvas.c b/src/api-impl-jni/android_graphics_Canvas.c index f264f4fb..5d7ae14c 100644 --- a/src/api-impl-jni/android_graphics_Canvas.c +++ b/src/api-impl-jni/android_graphics_Canvas.c @@ -115,3 +115,9 @@ JNIEXPORT void JNICALL Java_android_graphics_Canvas_native_1drawPath(JNIEnv *env sk_canvas_draw_path(canvas, path, paint); } + +JNIEXPORT void JNICALL Java_android_graphics_Canvas_native_1destroy_1canvas(JNIEnv *env, jclass class, jlong skia_canvas) +{ + sk_canvas_t *canvas = (sk_canvas_t *)_PTR(skia_canvas); + sk_canvas_destroy(canvas); +} diff --git a/src/api-impl-jni/defines.h b/src/api-impl-jni/defines.h index ec5f7df7..e81068f3 100644 --- a/src/api-impl-jni/defines.h +++ b/src/api-impl-jni/defines.h @@ -14,6 +14,8 @@ #define _INTPTR(ptr)((jlong)(intptr_t)(ptr)) #define _REF(obj)((*env)->NewGlobalRef(env, obj)) #define _UNREF(obj)((*env)->DeleteGlobalRef(env, obj)) +#define _WEAK_REF(obj)((*env)->NewWeakGlobalRef(env, obj)) +#define _WEAK_UNREF(obj)((*env)->DeleteWeakGlobalRef(env, obj)) #define _CLASS(object) ((*env)->GetObjectClass(env, object)) #define _SUPER(object) ((*env)->GetSuperclass(env, object)) #define _METHOD(class, method, attrs) ((*env)->GetMethodID(env, class, method, attrs)) diff --git a/src/api-impl-jni/generated_headers/android_graphics_Canvas.h b/src/api-impl-jni/generated_headers/android_graphics_Canvas.h index 62d2908c..3351d048 100644 --- a/src/api-impl-jni/generated_headers/android_graphics_Canvas.h +++ b/src/api-impl-jni/generated_headers/android_graphics_Canvas.h @@ -89,6 +89,14 @@ JNIEXPORT void JNICALL Java_android_graphics_Canvas_native_1rotate_1and_1transla JNIEXPORT void JNICALL Java_android_graphics_Canvas_native_1drawPath (JNIEnv *, jclass, jlong, jlong, jlong); +/* + * Class: android_graphics_Canvas + * Method: native_destroy_canvas + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_android_graphics_Canvas_native_1destroy_1canvas + (JNIEnv *, jclass, jlong); + #ifdef __cplusplus } #endif 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 55c14e95..025f5277 100644 --- a/src/api-impl-jni/generated_headers/android_view_View.h +++ b/src/api-impl-jni/generated_headers/android_view_View.h @@ -201,19 +201,19 @@ extern "C" { #define android_view_View_TEXT_DIRECTION_RTL 4L /* * Class: android_view_View - * Method: setOnTouchListener - * Signature: (Landroid/view/View/OnTouchListener;)V + * Method: nativeSetOnTouchListener + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_android_view_View_setOnTouchListener - (JNIEnv *, jobject, jobject); +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnTouchListener + (JNIEnv *, jobject, jlong); /* * Class: android_view_View - * Method: setOnClickListener - * Signature: (Landroid/view/View/OnClickListener;)V + * Method: nativeSetOnClickListener + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_android_view_View_setOnClickListener - (JNIEnv *, jobject, jobject); +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnClickListener + (JNIEnv *, jobject, jlong); /* * Class: android_view_View @@ -337,11 +337,11 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility /* * Class: android_view_View - * Method: setOnLongClickListener - * Signature: (Landroid/view/View/OnLongClickListener;)V + * Method: nativeSetOnLongClickListener + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_android_view_View_setOnLongClickListener - (JNIEnv *, jobject, jobject); +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnLongClickListener + (JNIEnv *, jobject, jlong); /* * Class: android_view_View diff --git a/src/api-impl-jni/generated_headers/android_widget_Button.h b/src/api-impl-jni/generated_headers/android_widget_Button.h index 22b82e01..e93ea795 100644 --- a/src/api-impl-jni/generated_headers/android_widget_Button.h +++ b/src/api-impl-jni/generated_headers/android_widget_Button.h @@ -217,11 +217,11 @@ JNIEXPORT void JNICALL Java_android_widget_Button_native_1setText /* * Class: android_widget_Button - * Method: native_setOnClickListener - * Signature: (JLandroid/view/View/OnClickListener;)V + * Method: nativeSetOnClickListener + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_android_widget_Button_native_1setOnClickListener - (JNIEnv *, jobject, jlong, jobject); +JNIEXPORT void JNICALL Java_android_widget_Button_nativeSetOnClickListener + (JNIEnv *, jobject, jlong); #ifdef __cplusplus } diff --git a/src/api-impl-jni/generated_headers/android_widget_ImageButton.h b/src/api-impl-jni/generated_headers/android_widget_ImageButton.h index eb8a4ef8..8deb4bf9 100644 --- a/src/api-impl-jni/generated_headers/android_widget_ImageButton.h +++ b/src/api-impl-jni/generated_headers/android_widget_ImageButton.h @@ -225,11 +225,11 @@ JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setDrawable /* * Class: android_widget_ImageButton - * Method: native_setOnClickListener - * Signature: (JLandroid/view/View/OnClickListener;)V + * Method: nativeSetOnClickListener + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setOnClickListener - (JNIEnv *, jobject, jlong, jobject); +JNIEXPORT void JNICALL Java_android_widget_ImageButton_nativeSetOnClickListener + (JNIEnv *, jobject, jlong); #ifdef __cplusplus } diff --git a/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c b/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c index 071b31b5..e6a80ba1 100644 --- a/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c +++ b/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c @@ -81,7 +81,7 @@ JNIEXPORT jlong JNICALL Java_android_graphics_drawable_Drawable_native_1construc JavaPaintable *paintable = NULL; if (handle_cache.drawable.draw != _METHOD(_CLASS(this), "draw", "(Landroid/graphics/Canvas;)V")) { paintable = g_object_new(java_paintable_get_type(), NULL); - paintable->drawable = _REF(this); + paintable->drawable = _WEAK_REF(this); } return _INTPTR(paintable); } diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 8e5ce482..6c2aefdb 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -133,6 +133,7 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.view.onInterceptTouchEvent = _METHOD(handle_cache.view.class, "onInterceptTouchEvent", "(Landroid/view/MotionEvent;)Z"); handle_cache.view.layoutInternal = _METHOD(handle_cache.view.class, "layoutInternal", "(II)V"); handle_cache.view.measure = _METHOD(handle_cache.view.class, "measure", "(II)V"); + handle_cache.view.performLongClick = _METHOD(handle_cache.view.class, "performLongClick", "(FF)Z"); handle_cache.asset_manager.class = _REF((*env)->FindClass(env, "android/content/res/AssetManager")); handle_cache.asset_manager.extractFromAPK = _STATIC_METHOD(handle_cache.asset_manager.class, "extractFromAPK", "(Ljava/lang/String;Ljava/lang/String;)V"); diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index 986cf9a1..9e4a715d 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -79,6 +79,7 @@ struct handle_cache { jmethodID onInterceptTouchEvent; jmethodID layoutInternal; jmethodID measure; + jmethodID performLongClick; } view; struct { jclass class; diff --git a/src/api-impl-jni/views/AndroidLayout.c b/src/api-impl-jni/views/AndroidLayout.c index 2100230a..2d3486f0 100644 --- a/src/api-impl-jni/views/AndroidLayout.c +++ b/src/api-impl-jni/views/AndroidLayout.c @@ -87,11 +87,20 @@ static GtkSizeRequestMode android_layout_get_request_mode(GtkLayoutManager *layo } } +static void android_layout_dispose(GObject *layout_manager) +{ + AndroidLayout *layout = ATL_ANDROID_LAYOUT(layout_manager); + JNIEnv *env = get_jni_env(); + _WEAK_UNREF(layout->view); +} + static void android_layout_class_init(AndroidLayoutClass *klass) { klass->parent_class.measure = android_layout_measure; klass->parent_class.allocate = android_layout_allocate; klass->parent_class.get_request_mode = android_layout_get_request_mode; + + klass->parent_class.parent_class.dispose = android_layout_dispose; } static void android_layout_init(AndroidLayout *self) {} @@ -101,7 +110,8 @@ G_DEFINE_TYPE(AndroidLayout, android_layout, GTK_TYPE_LAYOUT_MANAGER) GtkLayoutManager *android_layout_new(jobject view) { AndroidLayout *layout = g_object_new(android_layout_get_type(), NULL); - layout->view = view; + JNIEnv *env = get_jni_env(); + layout->view = _WEAK_REF(view); layout->width = MATCH_PARENT; layout->height = MATCH_PARENT; return &layout->parent_instance; diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index 558c7ef9..28728d36 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -13,34 +13,31 @@ #define SOURCE_TOUCHSCREEN 0x1002 -struct touch_callback_data { JavaVM *jvm; jobject this; jobject on_touch_listener; jclass on_touch_listener_class; bool intercepted; }; - static GdkEvent *canceled_event = NULL; -static struct touch_callback_data *cancel_triggerer = NULL; +static WrapperWidget *cancel_triggerer = NULL; -static bool call_ontouch_callback(int action, double x, double y, struct touch_callback_data *d, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event) +static bool call_ontouch_callback(WrapperWidget *wrapper, int action, double x, double y, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event) { bool ret; double raw_x; double raw_y; - JNIEnv *env; - (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); + JNIEnv *env = get_jni_env(); + jobject this = wrapper->jobj; gdk_event_get_position(event, &raw_x, &raw_y); jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, (float)x, (float)y, (long)timestamp, (float)raw_x, (float)raw_y); - if (phase == GTK_PHASE_CAPTURE && !d->intercepted) { - d->intercepted = (*env)->CallBooleanMethod(env, d->this, handle_cache.view.onInterceptTouchEvent, motion_event); - if (d->intercepted) { + if (phase == GTK_PHASE_CAPTURE && !wrapper->intercepting_touch) { + wrapper->intercepting_touch = (*env)->CallBooleanMethod(env, this, handle_cache.view.onInterceptTouchEvent, motion_event); + if (wrapper->intercepting_touch) { // store the event that was canceled and let it propagate to the child widgets canceled_event = event; - cancel_triggerer = d; + cancel_triggerer = wrapper; } ret = false; - } else if(d->on_touch_listener) /* NULL listener means the callback was registered for onTouchEvent */ - ret = (*env)->CallBooleanMethod(env, d->on_touch_listener, _METHOD(d->on_touch_listener_class, "onTouch", "(Landroid/view/View;Landroid/view/MotionEvent;)Z"), d->this, motion_event); - else - ret = (*env)->CallBooleanMethod(env, d->this, _METHOD(d->on_touch_listener_class, "onTouchEvent", "(Landroid/view/MotionEvent;)Z"), motion_event); + } else { + ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onTouchEvent, motion_event); + } if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); @@ -48,7 +45,7 @@ static bool call_ontouch_callback(int action, double x, double y, struct touch_c (*env)->DeleteLocalRef(env, motion_event); if (action == MOTION_EVENT_ACTION_UP) - d->intercepted = false; + wrapper->intercepting_touch = false; return ret; } static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *widget, double *x, double *y) @@ -70,41 +67,42 @@ static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *w } // TODO: find a way to reconcile this with libandroid/input.c? -static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *event, struct touch_callback_data *d) +static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *event, gpointer user_data) { double x; double y; GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(event_controller)); + WrapperWidget *wrapper = WRAPPER_WIDGET(widget); GtkPropagationPhase phase = gtk_event_controller_get_propagation_phase(GTK_EVENT_CONTROLLER(event_controller)); guint32 timestamp = gdk_event_get_time(event); // TODO: this doesn't work for multitouch - if (cancel_triggerer == d) { // cancel done + if (cancel_triggerer == wrapper) { // cancel done canceled_event = NULL; cancel_triggerer = NULL; } else if (event == canceled_event) { gdk_event_get_widget_relative_position(event, widget, &x, &y); - call_ontouch_callback(MOTION_EVENT_ACTION_CANCEL, x, y, d, phase, timestamp, event); + call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_CANCEL, x, y, phase, timestamp, event); return false; } switch(gdk_event_get_event_type(event)) { case GDK_BUTTON_PRESS: case GDK_TOUCH_BEGIN: gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(MOTION_EVENT_ACTION_DOWN, x, y, d, phase, timestamp, event); + return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_DOWN, x, y, phase, timestamp, event); break; case GDK_BUTTON_RELEASE: case GDK_TOUCH_END: gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(MOTION_EVENT_ACTION_UP, x, y, d, phase, timestamp, event); + return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_UP, x, y, phase, timestamp, event); break; case GDK_MOTION_NOTIFY: if (!(gdk_event_get_modifier_state(event) & GDK_BUTTON1_MASK)) break; case GDK_TOUCH_UPDATE: gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(MOTION_EVENT_ACTION_MOVE, x, y, d, phase, timestamp, event); + return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_MOVE, x, y, phase, timestamp, event); break; default: break; @@ -113,12 +111,13 @@ static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *e return false; } -static void on_click(GtkGestureClick *gesture, int n_press, double x, double y, struct touch_callback_data *d) +static void on_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) { - JNIEnv *env; - (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); + JNIEnv *env = get_jni_env(); + GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); + WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(widget)); - (*env)->CallVoidMethod(env, d->on_touch_listener, _METHOD(d->on_touch_listener_class, "onClick", "(Landroid/view/View;)V"), d->this); + (*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); @@ -146,25 +145,16 @@ static gboolean scroll_cb(GtkEventControllerScroll* self, gdouble dx, gdouble dy return ret; } -void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget, jobject on_touch_listener) +void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget) { - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - - struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data)); - callback_data->jvm = jvm; - callback_data->this = _REF(this); - callback_data->on_touch_listener = on_touch_listener ? _REF(on_touch_listener) : NULL; - callback_data->on_touch_listener_class = on_touch_listener ? _REF(_CLASS(callback_data->on_touch_listener)) : _REF(_CLASS(callback_data->this)); - GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_touch_listener"); if(old_controller) - gtk_widget_remove_controller(widget, old_controller); + return; GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new()); gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_BUBBLE); - g_signal_connect(controller, "event", G_CALLBACK(on_event), callback_data); + g_signal_connect(controller, "event", G_CALLBACK(on_event), NULL); gtk_widget_add_controller(widget, controller); g_object_set_data(G_OBJECT(widget), "on_touch_listener", controller); @@ -177,7 +167,7 @@ void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget, jobject o GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new()); gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_CAPTURE); - g_signal_connect(controller, "event", G_CALLBACK(on_event), callback_data); + g_signal_connect(controller, "event", G_CALLBACK(on_event), NULL); gtk_widget_add_controller(widget, controller); g_object_set_data(G_OBJECT(widget), "on_intercept_touch_listener", controller); } @@ -188,35 +178,24 @@ void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget, jobject o gtk_widget_set_overflow(GTK_WIDGET(wrapper), GTK_OVERFLOW_HIDDEN); } -JNIEXPORT void JNICALL Java_android_view_View_setOnTouchListener(JNIEnv *env, jobject this, jobject on_touch_listener) +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnTouchListener(JNIEnv *env, jobject this, jlong widget_ptr) { - GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget")))); + GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))); - _setOnTouchListener(env, this, widget, on_touch_listener); + _setOnTouchListener(env, this, widget); } -JNIEXPORT void JNICALL Java_android_view_View_setOnClickListener(JNIEnv *env, jobject this, jobject on_click_listener) +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr) { - GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget"))); - if (!on_click_listener) - return; - - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - - struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data)); - callback_data->jvm = jvm; - callback_data->this = _REF(this); - callback_data->on_touch_listener = _REF(on_click_listener); - callback_data->on_touch_listener_class = _REF(_CLASS(callback_data->on_touch_listener)); + GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_click_listener"); if(old_controller) - gtk_widget_remove_controller(widget, old_controller); + return; GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); - g_signal_connect(controller, "released", G_CALLBACK(on_click), callback_data); // the release completes the click, I guess? + g_signal_connect(controller, "released", G_CALLBACK(on_click), NULL); // the release completes the click, I guess? gtk_widget_add_controller(widget, controller); g_object_set_data(G_OBJECT(widget), "on_click_listener", controller); widget_set_needs_allocation(widget); @@ -351,15 +330,27 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility(JNIEnv *env, */ struct _JavaWidget {GtkWidget parent_instance;}; G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget) +static void java_widget_dispose(GObject *java_widget) +{ + GtkWidget *widget = GTK_WIDGET(java_widget); + GtkWidget *child = gtk_widget_get_first_child(widget); + while (child) { + GtkWidget *_child = gtk_widget_get_next_sibling(child); + gtk_widget_unparent(child); + child = _child; + } +} static void java_widget_init(JavaWidget *java_widget) {} -static void java_widget_class_init(JavaWidgetClass *class) {} +static void java_widget_class_init(JavaWidgetClass *class) { + G_OBJECT_CLASS(class)->dispose = java_widget_dispose; +} G_DEFINE_TYPE(JavaWidget, java_widget, GTK_TYPE_WIDGET) JNIEXPORT jlong JNICALL Java_android_view_View_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs) { - GtkWidget *wrapper = g_object_ref(wrapper_widget_new()); + WrapperWidget *wrapper = g_object_ref(WRAPPER_WIDGET(wrapper_widget_new())); GtkWidget *widget = g_object_new(java_widget_get_type(), NULL); - gtk_widget_set_layout_manager(widget, android_layout_new(_REF(this))); + gtk_widget_set_layout_manager(widget, android_layout_new(this)); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), widget); wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this); @@ -373,7 +364,7 @@ JNIEXPORT jlong JNICALL Java_android_view_View_native_1constructor(JNIEnv *env, if (_METHOD(_CLASS(this), "onGenericMotionEvent", "(Landroid/view/MotionEvent;)Z") != handle_cache.view.onGenericMotionEvent) { GtkEventController *controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); - g_signal_connect(controller, "scroll", G_CALLBACK(scroll_cb), _REF(this)); + g_signal_connect(controller, "scroll", G_CALLBACK(scroll_cb), wrapper->jobj); gtk_widget_add_controller(widget, controller); } @@ -522,12 +513,13 @@ JNIEXPORT jboolean JNICALL Java_android_view_View_native_1getGlobalVisibleRect(J return true; } -static void on_long_click(GtkGestureLongPress *gesture, double x, double y, struct touch_callback_data *d) +static void on_long_click(GtkGestureLongPress *gesture, double x, double y, gpointer user_data) { - JNIEnv *env; - (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); + JNIEnv *env = get_jni_env(); + GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); + WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(widget)); - bool ret =(*env)->CallBooleanMethod(env, d->on_touch_listener, _METHOD(d->on_touch_listener_class, "onLongClick", "(Landroid/view/View;)Z"), d->this); + bool ret =(*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performLongClick, x, y); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); @@ -536,7 +528,7 @@ static void on_long_click(GtkGestureLongPress *gesture, double x, double y, stru gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); } -static void on_long_click_update(GtkGesture *gesture, GdkEventSequence* sequence, struct touch_callback_data *d) +static void on_long_click_update(GtkGesture *gesture, GdkEventSequence* sequence, gpointer user_data) { GdkEvent *event = gtk_gesture_get_last_event(gesture, sequence); if (event == canceled_event) { @@ -544,29 +536,17 @@ static void on_long_click_update(GtkGesture *gesture, GdkEventSequence* sequence } } -JNIEXPORT void JNICALL Java_android_view_View_setOnLongClickListener(JNIEnv *env, jobject this, jobject on_long_click_listener) +JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnLongClickListener(JNIEnv *env, jobject this, jlong widget_ptr) { - GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget"))); - if (!on_long_click_listener) - return; - - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - - struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data)); - callback_data->jvm = jvm; - callback_data->this = _REF(this); - callback_data->on_touch_listener = _REF(on_long_click_listener); - callback_data->on_touch_listener_class = _REF(_CLASS(callback_data->on_touch_listener)); - + GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_long_click_listener"); if(old_controller) - gtk_widget_remove_controller(widget, old_controller); + return; GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_long_press_new()); - g_signal_connect(controller, "pressed", G_CALLBACK(on_long_click), callback_data); - g_signal_connect(controller, "update", G_CALLBACK(on_long_click_update), callback_data); + g_signal_connect(controller, "pressed", G_CALLBACK(on_long_click), NULL); + g_signal_connect(controller, "update", G_CALLBACK(on_long_click_update), NULL); gtk_widget_add_controller(widget, controller); g_object_set_data(G_OBJECT(widget), "on_long_click_listener", controller); } diff --git a/src/api-impl-jni/widgets/WrapperWidget.c b/src/api-impl-jni/widgets/WrapperWidget.c index d9eff71f..5d617888 100644 --- a/src/api-impl-jni/widgets/WrapperWidget.c +++ b/src/api-impl-jni/widgets/WrapperWidget.c @@ -17,13 +17,19 @@ static void wrapper_widget_init (WrapperWidget *wrapper_widget) static void wrapper_widget_dispose(GObject *wrapper_widget) { - gtk_widget_unparent(gtk_widget_get_first_child(GTK_WIDGET(wrapper_widget))); + GtkWidget *widget = GTK_WIDGET(wrapper_widget); + GtkWidget *child = gtk_widget_get_first_child(widget); + while (child) { + GtkWidget *_child = gtk_widget_get_next_sibling(child); + gtk_widget_unparent(child); + child = _child; + } WrapperWidget *wrapper = WRAPPER_WIDGET(wrapper_widget); if (wrapper->jvm) { JNIEnv *env; (*wrapper->jvm)->GetEnv(wrapper->jvm, (void**)&env, JNI_VERSION_1_6); if (wrapper->jobj) - _UNREF(wrapper->jobj); + _WEAK_UNREF(wrapper->jobj); if (wrapper->canvas) _UNREF(wrapper->canvas); } @@ -179,7 +185,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job JavaVM *jvm; (*env)->GetJavaVM(env, &jvm); wrapper->jvm = jvm; - wrapper->jobj = _REF(jobj); + wrapper->jobj = _WEAK_REF(jobj); jmethodID on_draw_method = _METHOD(_CLASS(jobj), "onDraw", "(Landroid/graphics/Canvas;)V"); jmethodID draw_method = _METHOD(_CLASS(jobj), "draw", "(Landroid/graphics/Canvas;)V"); if (on_draw_method != handle_cache.view.onDraw || draw_method != handle_cache.view.draw) { @@ -192,7 +198,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job jmethodID ontouchevent_method = _METHOD(_CLASS(jobj), "onTouchEvent", "(Landroid/view/MotionEvent;)Z"); if (ontouchevent_method != handle_cache.view.onTouchEvent) { - _setOnTouchListener(env, jobj, GTK_WIDGET(wrapper), NULL); + _setOnTouchListener(env, jobj, GTK_WIDGET(wrapper)); } jmethodID computeScroll_method = _METHOD(_CLASS(jobj), "computeScroll", "()V"); @@ -204,7 +210,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job if (performClick_method != handle_cache.view.performClick) { GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); - g_signal_connect(controller, "released", G_CALLBACK(on_click), _REF(jobj)); + g_signal_connect(controller, "released", G_CALLBACK(on_click), wrapper->jobj); gtk_widget_add_controller(wrapper->child, controller); widget_set_needs_allocation(wrapper->child); } diff --git a/src/api-impl-jni/widgets/WrapperWidget.h b/src/api-impl-jni/widgets/WrapperWidget.h index 9ca71bb7..d10fd41b 100644 --- a/src/api-impl-jni/widgets/WrapperWidget.h +++ b/src/api-impl-jni/widgets/WrapperWidget.h @@ -22,6 +22,7 @@ struct _WrapperWidget int real_width; int real_height; gboolean needs_allocation; + gboolean intercepting_touch; }; struct _WrapperWidgetClass @@ -37,6 +38,6 @@ void wrapper_widget_set_layout_params(WrapperWidget *wrapper, int width, int hei void wrapper_widget_set_background(WrapperWidget *wrapper, GdkPaintable *paintable); void wrapper_widget_consume_touch_events(WrapperWidget *wrapper); -void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget, jobject on_touch_listener); +void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget); #endif diff --git a/src/api-impl-jni/widgets/android_widget_AbsListView.c b/src/api-impl-jni/widgets/android_widget_AbsListView.c index c09375e9..71bde73d 100644 --- a/src/api-impl-jni/widgets/android_widget_AbsListView.c +++ b/src/api-impl-jni/widgets/android_widget_AbsListView.c @@ -107,7 +107,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_AbsListView_native_1constructor(JNIE RangeListModel *model = g_object_new(range_list_model_get_type(), NULL); GtkWidget *list_view = gtk_list_view_new(GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(model))), factory); model->list_view = list_view; - model->jobject = _REF(this); + model->jobject = _WEAK_REF(this); GtkWidget *scrolled_window = gtk_scrolled_window_new(); gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(scrolled_window), TRUE); gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(scrolled_window), TRUE); diff --git a/src/api-impl-jni/widgets/android_widget_AbsSpinner.c b/src/api-impl-jni/widgets/android_widget_AbsSpinner.c index e4a1c834..b1f3d402 100644 --- a/src/api-impl-jni/widgets/android_widget_AbsSpinner.c +++ b/src/api-impl-jni/widgets/android_widget_AbsSpinner.c @@ -36,7 +36,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_AbsSpinner_native_1constructor(JNIEn GtkWidget *dropdown = gtk_drop_down_new(G_LIST_MODEL(model), NULL); gtk_drop_down_set_factory(GTK_DROP_DOWN(dropdown), factory); model->list_view = dropdown; - model->jobject = _REF(this); + model->jobject = _WEAK_REF(this); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), dropdown); gtk_widget_set_name(dropdown, "Spinner"); return _INTPTR(dropdown); diff --git a/src/api-impl-jni/widgets/android_widget_Button.c b/src/api-impl-jni/widgets/android_widget_Button.c index 376ca376..949037b3 100644 --- a/src/api-impl-jni/widgets/android_widget_Button.c +++ b/src/api-impl-jni/widgets/android_widget_Button.c @@ -15,6 +15,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_Button_native_1constructor(JNIEnv *e GtkWidget *label = gtk_button_new_with_label(text); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), label); wrapper_widget_consume_touch_events(WRAPPER_WIDGET(wrapper)); // Android button consumes touch events + wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this); return _INTPTR(label); } @@ -27,38 +28,20 @@ JNIEXPORT void JNICALL Java_android_widget_Button_native_1setText(JNIEnv *env, j ((*env)->ReleaseStringUTFChars(env, text, nativeText)); } -struct touch_callback_data { - JavaVM *jvm; - jobject this; - jobject listener; - jmethodID listener_method; -}; +static void clicked_cb(GtkWidget *button, gpointer user_data) { + JNIEnv *env = get_jni_env(); + WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(button)); -static void clicked_cb(GtkWidget *button, struct touch_callback_data *d) { - JNIEnv *env; - (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); - - (*env)->CallVoidMethod(env, d->listener, d->listener_method, d->this); + (*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); } -JNIEXPORT void JNICALL Java_android_widget_Button_native_1setOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr, jobject on_click_listener) +JNIEXPORT void JNICALL Java_android_widget_Button_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr) { GtkWidget *button = GTK_WIDGET(_PTR(widget_ptr)); g_signal_handlers_disconnect_matched(button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, clicked_cb, NULL); - if (!on_click_listener) - return; - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - - struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data)); - callback_data->jvm = jvm; - callback_data->this = _REF(this); - callback_data->listener = _REF(on_click_listener); - callback_data->listener_method = _METHOD(_CLASS(on_click_listener), "onClick", "(Landroid/view/View;)V"); - - g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), callback_data); + g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), NULL); } diff --git a/src/api-impl-jni/widgets/android_widget_EditText.c b/src/api-impl-jni/widgets/android_widget_EditText.c index a395ea8a..f12ceea2 100644 --- a/src/api-impl-jni/widgets/android_widget_EditText.c +++ b/src/api-impl-jni/widgets/android_widget_EditText.c @@ -89,7 +89,7 @@ JNIEXPORT void JNICALL Java_android_widget_EditText_native_1setOnEditorActionLis return; struct changed_callback_data *callback_data = malloc(sizeof(struct changed_callback_data)); - callback_data->this = _REF(this); + callback_data->this = _WEAK_REF(this); callback_data->listener = _REF(listener); callback_data->listener_method = _METHOD(_CLASS(listener), "onEditorAction", "(Landroid/widget/TextView;ILandroid/view/KeyEvent;)Z"); diff --git a/src/api-impl-jni/widgets/android_widget_ImageButton.c b/src/api-impl-jni/widgets/android_widget_ImageButton.c index 1c41a4a4..d4dc4ea5 100644 --- a/src/api-impl-jni/widgets/android_widget_ImageButton.c +++ b/src/api-impl-jni/widgets/android_widget_ImageButton.c @@ -26,41 +26,23 @@ JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setPixbuf(JNIEnv Java_android_widget_ImageButton_native_1setDrawable(env, this, widget_ptr, _INTPTR(paintable)); } -struct touch_callback_data { - JavaVM *jvm; - jobject this; - jobject listener; - jmethodID listener_method; -}; - -static void clicked_cb(GtkWidget *button, struct touch_callback_data *d) { +static void clicked_cb(GtkWidget *button, gpointer user_data) { printf("clicked_cb\n"); - JNIEnv *env; - (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); + JNIEnv *env = get_jni_env(); + WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(button)); - (*env)->CallVoidMethod(env, d->listener, d->listener_method, d->this); + (*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); } -JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr, jobject on_click_listener) +JNIEXPORT void JNICALL Java_android_widget_ImageButton_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr) { GtkWidget *button = GTK_WIDGET(_PTR(widget_ptr)); g_signal_handlers_disconnect_matched(button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, clicked_cb, NULL); - if (!on_click_listener) - return; - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - - struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data)); - callback_data->jvm = jvm; - callback_data->this = _REF(this); - callback_data->listener = _REF(on_click_listener); - callback_data->listener_method = _METHOD(_CLASS(on_click_listener), "onClick", "(Landroid/view/View;)V"); - - g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), callback_data); + g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), NULL); } JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setDrawable(JNIEnv *env, jobject this, jlong widget_ptr, jlong paintable_ptr) diff --git a/src/api-impl-jni/widgets/android_widget_TextView.c b/src/api-impl-jni/widgets/android_widget_TextView.c index 00db6ea7..29ca94e5 100644 --- a/src/api-impl-jni/widgets/android_widget_TextView.c +++ b/src/api-impl-jni/widgets/android_widget_TextView.c @@ -30,6 +30,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_TextView_native_1constructor(JNIEnv gtk_widget_set_hexpand(label, TRUE); gtk_box_append(GTK_BOX(box), label); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), box); + wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this); PangoAttrList* pango_attrs = pango_attr_list_new(); pango_attr_list_insert(pango_attrs, pango_attr_font_features_new("tnum")); diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index 7f13705b..0055327e 100644 --- a/src/api-impl/android/app/Activity.java +++ b/src/api-impl/android/app/Activity.java @@ -370,6 +370,8 @@ public class Activity extends ContextThemeWrapper implements Window.Callback { @Override public void run() { nativeFinish(getWindow().native_window); + getWindow().setContentView(null); + window = null; } }); } diff --git a/src/api-impl/android/graphics/Bitmap.java b/src/api-impl/android/graphics/Bitmap.java index 64f27581..bc5f21fc 100644 --- a/src/api-impl/android/graphics/Bitmap.java +++ b/src/api-impl/android/graphics/Bitmap.java @@ -1596,6 +1596,18 @@ public final class Bitmap { } } + @Override + @SuppressWarnings("deprecation") + protected void finalize() throws Throwable { + try { + super.finalize(); + } finally { + if (!isRecycled()) { + recycle(); + } + } + } + /** * internal ATL method to create or get a GdkTexture for the pixbuf * @return pointer to the GdkTexture diff --git a/src/api-impl/android/graphics/Canvas.java b/src/api-impl/android/graphics/Canvas.java index 793a7cfd..309d3f01 100644 --- a/src/api-impl/android/graphics/Canvas.java +++ b/src/api-impl/android/graphics/Canvas.java @@ -19,6 +19,19 @@ public class Canvas { this.widget = widget; } + @Override + @SuppressWarnings("deprecation") + protected void finalize() throws Throwable { + try { + super.finalize(); + } finally { + if (skia_canvas != 0) { + native_destroy_canvas(skia_canvas); + skia_canvas = 0; + } + } + } + // FIXME: are these _needed_ ? public int save() { @@ -444,4 +457,5 @@ public class Canvas { private static native void native_rotate(long skia_canvas, long widget, float angle); private static native void native_rotate_and_translate(long skia_canvas, long widget, float angle, float tx, float ty); private static native void native_drawPath(long skia_canvas, long path, long skia_paint); + private static native void native_destroy_canvas(long skia_canvas); } diff --git a/src/api-impl/android/media/MediaCodec.java b/src/api-impl/android/media/MediaCodec.java index 1c89a839..386e7646 100644 --- a/src/api-impl/android/media/MediaCodec.java +++ b/src/api-impl/android/media/MediaCodec.java @@ -18,6 +18,7 @@ public class MediaCodec { private long native_codec; private boolean outputFormatSet = false; private MediaFormat mediaFormat; + private boolean isReleased = false; private Queue freeOutputBuffers; private Queue queuedInputBuffers; @@ -148,6 +149,19 @@ public class MediaCodec { public void release() { System.out.println("MediaCodec.release(): codecName=" + codecName); native_release(native_codec); + isReleased = true; + } + + @Override + @SuppressWarnings("deprecation") + protected void finalize() throws Throwable { + try { + super.finalize(); + } finally { + if (!isReleased) { + release(); + } + } } private native long native_constructor(String codecName); diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index 46c7b172..999d6510 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -616,7 +616,7 @@ public class View implements Drawable.Callback { } public interface OnLongClickListener { - // TODO + public boolean onLongClick(View v); } public interface OnHoverListener { @@ -964,12 +964,25 @@ public class View implements Drawable.Callback { this.gravity = gravity; } + private OnTouchListener on_touch_listener = null; public boolean onTouchEvent(MotionEvent event) { - return false; + if (on_touch_listener != null) + return on_touch_listener.onTouch(this, event); + else + return false; } - public native void setOnTouchListener(OnTouchListener l); - public native void setOnClickListener(OnClickListener l); + public void setOnTouchListener(OnTouchListener l) { + nativeSetOnTouchListener(widget); + on_touch_listener = l; + } + protected native void nativeSetOnTouchListener(long widget); + private OnClickListener on_click_listener = null; + public void setOnClickListener(OnClickListener l) { + nativeSetOnClickListener(widget); + on_click_listener = l; + } + protected native void nativeSetOnClickListener(long widget); public /*native*/ void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {} public native final int getWidth(); public native final int getHeight(); @@ -1200,7 +1213,18 @@ public class View implements Drawable.Callback { return null; } - public native void setOnLongClickListener(OnLongClickListener listener); + private OnLongClickListener on_long_click_listener = null; + public boolean performLongClick(float x, float y) { + if (on_long_click_listener != null) { + return on_long_click_listener.onLongClick(this); + } + return false; + } + public void setOnLongClickListener(OnLongClickListener listener) { + nativeSetOnLongClickListener(widget); + on_long_click_listener = listener; + } + protected native void nativeSetOnLongClickListener(long widget); public void setLongClickable(boolean longClickable) {} @@ -1398,6 +1422,7 @@ public class View implements Drawable.Callback { public boolean isInEditMode() {return false;} @Override + @SuppressWarnings("deprecation") protected void finalize() throws Throwable { try { native_destructor(widget); @@ -1504,7 +1529,12 @@ public class View implements Drawable.Callback { public void setDuplicateParentStateEnabled(boolean enabled) {} public boolean performClick() { - return false; + if (on_click_listener != null) { + on_click_listener.onClick(this); + return true; + } else { + return false; + } } public void playSoundEffect(int soundConstant) {} @@ -1662,6 +1692,9 @@ public class View implements Drawable.Callback { protected void onAttachedToWindow () { attachedToWindow = true; } + protected void onDetachedFromWindow() { + attachedToWindow = false; + } public void setLayerType(int layerType, Paint paint) {} diff --git a/src/api-impl/android/view/ViewGroup.java b/src/api-impl/android/view/ViewGroup.java index 4a8636d1..25587f26 100644 --- a/src/api-impl/android/view/ViewGroup.java +++ b/src/api-impl/android/view/ViewGroup.java @@ -91,6 +91,8 @@ public class ViewGroup extends View implements ViewParent, ViewManager { child.parent = null; children.remove(child); native_removeView(widget, child.widget); + if (isAttachedToWindow()) + child.onDetachedFromWindow(); if (onHierarchyChangeListener != null) { onHierarchyChangeListener.onChildViewRemoved(this, child); } @@ -114,6 +116,8 @@ public class ViewGroup extends View implements ViewParent, ViewManager { child.parent = null; it.remove(); native_removeView(widget, child.widget); + if (isAttachedToWindow()) + child.onDetachedFromWindow(); if (onHierarchyChangeListener != null) { onHierarchyChangeListener.onChildViewRemoved(this, child); } @@ -317,6 +321,14 @@ public class ViewGroup extends View implements ViewParent, ViewManager { } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (View child: children) { + child.onDetachedFromWindow(); + } + } + protected boolean isChildrenDrawingOrderEnabled() { return false; } @Override diff --git a/src/api-impl/android/view/Window.java b/src/api-impl/android/view/Window.java index f4585dbd..dbb7bed1 100644 --- a/src/api-impl/android/view/Window.java +++ b/src/api-impl/android/view/Window.java @@ -46,9 +46,16 @@ public class Window { } public void setContentView(View view) { + if (contentView != view) { + if (contentView != null) + contentView.onDetachedFromWindow(); + if (view != null) + view.onAttachedToWindow(); + } contentView = view; - view.onAttachedToWindow(); - set_widget_as_root(native_window, view.widget); + if (view != null) { + set_widget_as_root(native_window, view.widget); + } } public View getDecorView() { diff --git a/src/api-impl/android/widget/Button.java b/src/api-impl/android/widget/Button.java index 5ccdfe9a..3b51884e 100644 --- a/src/api-impl/android/widget/Button.java +++ b/src/api-impl/android/widget/Button.java @@ -24,7 +24,8 @@ public class Button extends TextView { @Override protected native long native_constructor(Context context, AttributeSet attrs); public native final void native_setText(long widget, String text); - protected native void native_setOnClickListener(long widget, OnClickListener l); + @Override + protected native void nativeSetOnClickListener(long widget); @Override public void setText(CharSequence text) { @@ -37,11 +38,6 @@ public class Button extends TextView { @Override public void setTextSize(float size) {} - @Override - public void setOnClickListener(final OnClickListener l) { - native_setOnClickListener(widget, l); - } - @Override public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {} diff --git a/src/api-impl/android/widget/ImageButton.java b/src/api-impl/android/widget/ImageButton.java index 6a14e373..9d945869 100644 --- a/src/api-impl/android/widget/ImageButton.java +++ b/src/api-impl/android/widget/ImageButton.java @@ -23,11 +23,7 @@ public class ImageButton extends ImageView { protected native void native_setPixbuf(long widget, long pixbuf); @Override protected native void native_setDrawable(long widget, long paintable); - protected native void native_setOnClickListener(long widget, OnClickListener l); - @Override - public void setOnClickListener(final OnClickListener l) { - native_setOnClickListener(widget, l); - } + protected native void nativeSetOnClickListener(long widget); } diff --git a/src/api-impl/android/widget/TextView.java b/src/api-impl/android/widget/TextView.java index 8583729c..45182175 100644 --- a/src/api-impl/android/widget/TextView.java +++ b/src/api-impl/android/widget/TextView.java @@ -169,12 +169,22 @@ public class TextView extends View { protected native void native_setCompoundDrawables(long widget, long left, long top, long right, long bottom); + // just to prevent garbage collection while native side uses it + private Drawable drawableLeft = null; + private Drawable drawableTop = null; + private Drawable drawableRight = null; + private Drawable drawableBottom = null; + public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { native_setCompoundDrawables(widget, left != null ? left.paintable : 0, top != null ? top.paintable : 0, right != null ? right.paintable : 0, bottom != null ? bottom.paintable : 0); + drawableLeft = left; + drawableTop = top; + drawableRight = right; + drawableBottom = bottom; } public void setAllCaps(boolean allCaps) {}