From 336c3674119f61bd78a056476769ce83b97230bb Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 10 Oct 2023 13:11:14 +0800 Subject: [PATCH] Implement frame restacking under Android * java/org/gnu/emacs/EmacsActivity.java (invalidateFocus1): Synchronize with window.children for iteration through it. * java/org/gnu/emacs/EmacsService.java (queryTree): Synchronize with windowList for iteration through it. * java/org/gnu/emacs/EmacsView.java (moveChildToBack): Correct formatting mistake. (moveAbove, moveBelow): New functions. * java/org/gnu/emacs/EmacsWindow.java (destroyHandle, reparentTo) (raise, lower): Remedy synchronization blunders. (reconfigure): New function. * src/android.c (android_init_emacs_window): Link with `reconfigure'. (android_reconfigure_wm_window): New wrapper function. * src/androidfns.c (android_frame_restack): New function. (Fandroid_frame_restack): Properly implement this function and expunge outdated comment. * src/androidgui.h (enum android_stack_mode) (enum android_window_changes): New enumerators. --- java/org/gnu/emacs/EmacsActivity.java | 7 +- java/org/gnu/emacs/EmacsService.java | 17 ++-- java/org/gnu/emacs/EmacsView.java | 40 +++++++- java/org/gnu/emacs/EmacsWindow.java | 130 +++++++++++++++++++++++--- src/android.c | 33 +++++++ src/androidfns.c | 49 ++++++++-- src/androidgui.h | 21 +++++ 7 files changed, 264 insertions(+), 33 deletions(-) diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index cecd9c21d99..f9aa261e355 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -89,8 +89,11 @@ public class EmacsActivity extends Activity if (window.view.isFocused ()) focusedWindow = window; - for (EmacsWindow child : window.children) - invalidateFocus1 (child); + synchronized (window.children) + { + for (EmacsWindow child : window.children) + invalidateFocus1 (child); + } } public static void diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 28b725d0cd0..6fa2ebb3fdb 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -505,15 +505,18 @@ invocation of app_process (through android-emacs) can else windowList = window.children; - array = new short[windowList.size () + 1]; - i = 1; + synchronized (windowList) + { + array = new short[windowList.size () + 1]; + i = 1; - array[0] = (window == null - ? 0 : (window.parent != null - ? window.parent.handle : 0)); + array[0] = (window == null + ? 0 : (window.parent != null + ? window.parent.handle : 0)); - for (EmacsWindow treeWindow : windowList) - array[i++] = treeWindow.handle; + for (EmacsWindow treeWindow : windowList) + array[i++] = treeWindow.handle; + } return array; } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index d09dcc7e50d..877b1ce2429 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -581,12 +581,12 @@ else if (child.getVisibility () != GONE) /* The view at 0 is the surface view. */ attachViewToParent (child, 1, - child.getLayoutParams()); + child.getLayoutParams ()); } } - /* The following two functions must not be called if the view has no - parent, or is parented to an activity. */ + /* The following four functions must not be called if the view has + no parent, or is parented to an activity. */ public void raise () @@ -615,6 +615,40 @@ else if (child.getVisibility () != GONE) parent.moveChildToBack (this); } + public void + moveAbove (EmacsView view) + { + EmacsView parent; + int index; + + parent = (EmacsView) getParent (); + + if (parent != view.getParent ()) + throw new IllegalStateException ("Moving view above non-sibling"); + + index = parent.indexOfChild (this); + parent.detachViewFromParent (index); + index = parent.indexOfChild (view); + parent.attachViewToParent (this, index + 1, getLayoutParams ()); + } + + public void + moveBelow (EmacsView view) + { + EmacsView parent; + int index; + + parent = (EmacsView) getParent (); + + if (parent != view.getParent ()) + throw new IllegalStateException ("Moving view above non-sibling"); + + index = parent.indexOfChild (this); + parent.detachViewFromParent (index); + index = parent.indexOfChild (view); + parent.attachViewToParent (this, index, getLayoutParams ()); + } + @Override protected void onCreateContextMenu (ContextMenu menu) diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 1f28d5f4f53..8d444aa27f5 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -22,6 +22,7 @@ import java.lang.IllegalStateException; import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -93,7 +94,9 @@ private static class Coordinate public EmacsWindow parent; /* List of all children in stacking order. This must be kept - consistent with their Z order! */ + consistent with their Z order! + + Synchronize access to this list with itself. */ public ArrayList children; /* Map between pointer identifiers and last known position. Used to @@ -165,7 +168,11 @@ private static class Coordinate if (parent != null) { - parent.children.add (this); + synchronized (parent.children) + { + parent.children.add (this); + } + EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override public void @@ -214,7 +221,12 @@ private static class Coordinate destroyHandle () throws IllegalStateException { if (parent != null) - parent.children.remove (this); + { + synchronized (parent.children) + { + parent.children.remove (this); + } + } EmacsActivity.invalidateFocus (); @@ -1163,10 +1175,20 @@ private static class Coordinate /* Reparent this window to the other window. */ if (parent != null) - parent.children.remove (this); + { + synchronized (parent.children) + { + parent.children.remove (this); + } + } if (otherWindow != null) - otherWindow.children.add (this); + { + synchronized (otherWindow.children) + { + otherWindow.children.add (this); + } + } parent = otherWindow; @@ -1239,9 +1261,12 @@ else if (EmacsWindow.this.isMapped) if (parent == null) return; - /* Remove and add this view again. */ - parent.children.remove (this); - parent.children.add (this); + synchronized (parent.children) + { + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + } /* Request a relayout. */ EmacsService.SERVICE.runOnUiThread (new Runnable () { @@ -1261,9 +1286,12 @@ else if (EmacsWindow.this.isMapped) if (parent == null) return; - /* Remove and add this view again. */ - parent.children.remove (this); - parent.children.add (this); + synchronized (parent.children) + { + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + } /* Request a relayout. */ EmacsService.SERVICE.runOnUiThread (new Runnable () { @@ -1276,6 +1304,86 @@ else if (EmacsWindow.this.isMapped) }); } + public synchronized void + reconfigure (final EmacsWindow window, final int stackMode) + { + ListIterator iterator; + EmacsWindow object; + + /* This does nothing here. */ + if (parent == null) + return; + + /* If window is NULL, call lower or upper subject to + stackMode. */ + + if (window == null) + { + if (stackMode == 1) /* ANDROID_BELOW */ + lower (); + else + raise (); + + return; + } + + /* Otherwise, if window.parent is distinct from this, return. */ + if (window.parent != this.parent) + return; + + /* Synchronize with the parent's child list. Iterate over each + item until WINDOW is encountered, before moving this window to + the location prescribed by STACKMODE. */ + + synchronized (parent.children) + { + /* Remove this window from parent.children, for it will be + reinserted before or after WINDOW. */ + parent.children.remove (this); + + /* Create an iterator. */ + iterator = parent.children.listIterator (); + + while (iterator.hasNext ()) + { + object = iterator.next (); + + if (object == window) + { + /* Now place this before or after the cursor of the + iterator. */ + + if (stackMode == 0) /* ANDROID_ABOVE */ + iterator.add (this); + else + { + iterator.previous (); + iterator.add (this); + } + + /* Effect the same adjustment upon the view + hiearchy. */ + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + if (stackMode == 0) + view.moveAbove (window.view); + else + view.moveBelow (window.view); + } + }); + } + } + + /* parent.children does not list WINDOW, which should never + transpire. */ + EmacsNative.emacsAbort (); + } + } + public synchronized int[] getWindowGeometry () { diff --git a/src/android.c b/src/android.c index b9236075a1e..d1182698669 100644 --- a/src/android.c +++ b/src/android.c @@ -104,6 +104,7 @@ struct android_emacs_window jmethodID make_input_focus; jmethodID raise; jmethodID lower; + jmethodID reconfigure; jmethodID get_window_geometry; jmethodID translate_coordinates; jmethodID set_dont_accept_focus; @@ -1755,6 +1756,7 @@ android_init_emacs_window (void) FIND_METHOD (make_input_focus, "makeInputFocus", "(J)V"); FIND_METHOD (raise, "raise", "()V"); FIND_METHOD (lower, "lower", "()V"); + FIND_METHOD (reconfigure, "reconfigure", "(Lorg/gnu/emacs/EmacsWindow;I)V"); FIND_METHOD (get_window_geometry, "getWindowGeometry", "()[I"); FIND_METHOD (translate_coordinates, "translateCoordinates", @@ -4963,6 +4965,37 @@ android_lower_window (android_window handle) android_exception_check (); } +void +android_reconfigure_wm_window (android_window handle, + enum android_wc_value_mask value_mask, + struct android_window_changes *values) +{ + jobject sibling, window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + if (!(value_mask & ANDROID_CW_STACK_MODE)) + return; + + /* If value_mask & ANDROID_CW_SIBLING, place HANDLE above or below + values->sibling pursuant to values->stack_mode; else, reposition + it at the top or the bottom of its parent. */ + + sibling = NULL; + + if (value_mask & ANDROID_CW_SIBLING) + sibling = android_resolve_handle (values->sibling, + ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.reconfigure, + sibling, + (jint) values->stack_mode); + android_exception_check (); +} + int android_query_tree (android_window handle, android_window *root_return, android_window *parent_return, diff --git a/src/androidfns.c b/src/androidfns.c index 3ee9f7634aa..772a4f51e78 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -1591,7 +1591,8 @@ and width values are in pixels. #endif } -DEFUN ("android-frame-edges", Fandroid_frame_edges, Sandroid_frame_edges, 0, 2, 0, +DEFUN ("android-frame-edges", Fandroid_frame_edges, + Sandroid_frame_edges, 0, 2, 0, doc: /* Return edge coordinates of FRAME. FRAME must be a live frame and defaults to the selected one. The return value is a list of the form (LEFT, TOP, RIGHT, BOTTOM). All values are @@ -1693,6 +1694,28 @@ TERMINAL is a frame. */) #endif } +#ifndef ANDROID_STUBIFY + +static void +android_frame_restack (struct frame *f1, struct frame *f2, + bool above_flag) +{ + android_window window1; + struct android_window_changes wc; + unsigned long mask; + + window1 = FRAME_ANDROID_WINDOW (f1); + wc.sibling = FRAME_ANDROID_WINDOW (f2); + wc.stack_mode = above_flag ? ANDROID_ABOVE : ANDROID_BELOW; + mask = ANDROID_CW_SIBLING | ANDROID_CW_STACK_MODE; + + block_input (); + android_reconfigure_wm_window (window1, mask, &wc); + unblock_input (); +} + +#endif /* !ANDROID_STUBIFY */ + DEFUN ("android-frame-restack", Fandroid_frame_restack, Sandroid_frame_restack, 2, 3, 0, doc: /* Restack FRAME1 below FRAME2. @@ -1709,19 +1732,25 @@ that of FRAME2. Hence the position of FRAME2 in its display's Z \(stacking) order relative to all other frames excluding FRAME1 remains unaltered. -The Android system refuses to restack windows, so this does not -work. */) - (Lisp_Object frame1, Lisp_Object frame2, Lisp_Object frame3) +Android does not facilitate restacking top-level windows managed by +its own window manager; nor is it possible to restack frames that are +children of different parents. Consequently, this function only +functions when FRAME1 and FRAME2 are both child frames subordinate to +the same parent frame. */) + (Lisp_Object frame1, Lisp_Object frame2, Lisp_Object above) { #ifdef ANDROID_STUBIFY error ("Android cross-compilation stub called!"); return Qnil; -#else - /* This is not supported on Android because of limitations in the - platform that prevent ViewGroups from restacking - SurfaceViews. */ - return Qnil; -#endif +#else /* !ANDROID_STUBIFY */ + struct frame *f1 = decode_live_frame (frame1); + struct frame *f2 = decode_live_frame (frame2); + + if (!(FRAME_ANDROID_WINDOW (f1) && FRAME_ANDROID_WINDOW (f2))) + error ("Cannot restack frames"); + android_frame_restack (f1, f2, !NILP (above)); + return Qt; +#endif /* ANDROID_STUBIFY */ } DEFUN ("android-mouse-absolute-pixel-position", diff --git a/src/androidgui.h b/src/androidgui.h index 936706b092e..b58c39a5276 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -564,6 +564,24 @@ enum android_ic_mode ANDROID_IC_MODE_TEXT = 2, }; +enum android_stack_mode + { + ANDROID_ABOVE = 0, + ANDROID_BELOW = 1, + }; + +enum android_wc_value_mask + { + ANDROID_CW_SIBLING = 0, + ANDROID_CW_STACK_MODE = 1, + }; + +struct android_window_changes +{ + android_window sibling; + enum android_stack_mode stack_mode; +}; + extern int android_pending (void); extern void android_next_event (union android_event *); extern bool android_check_if_event (union android_event *, @@ -643,6 +661,9 @@ extern void android_bell (void); extern void android_set_input_focus (android_window, unsigned long); extern void android_raise_window (android_window); extern void android_lower_window (android_window); +extern void android_reconfigure_wm_window (android_window, + enum android_wc_value_mask, + struct android_window_changes *); extern int android_query_tree (android_window, android_window *, android_window *, android_window **, unsigned int *);