From c8f49c9276d34741bfbe7752dd38391c0b8d782b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 13:17:43 +0800 Subject: [PATCH] Implement `fullscreen' on Android 4.0 and later * doc/emacs/android.texi (Android Windowing): Document what new frame parameters are now supported. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `isFullscreen'. (detachWindow, attachWindow): Sync fullscreen state. (onWindowFocusChanged): Add more logging. (onResume): Restore previous fullscreen state. (syncFullscreen): New function. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (setFullscreen): New function. * src/android.c (struct android_emacs_window): Add new method. (android_init_emacs_window): Look up new method. (android_set_fullscreen): New function. * src/androidgui.h: * src/androidterm.c (android_fullscreen_hook): Implement accordingly. --- doc/emacs/android.texi | 18 ++--- java/org/gnu/emacs/EmacsActivity.java | 107 +++++++++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 26 +++++++ src/android.c | 32 ++++++++ src/androidgui.h | 1 + src/androidterm.c | 24 +++++- 6 files changed, 193 insertions(+), 15 deletions(-) diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 35f0ba55cf4..4e5402f5f40 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -355,8 +355,8 @@ tiled on the screen at any time. are created. Instead, the system may choose to terminate windows that are not on screen in order to save memory, with the assumption that the program will save its contents to disk and restore them later, -when the user asks to open it again. As this is obvious not possible -with Emacs, Emacs separates a frame from a system window. +when the user asks to open it again. As this is obviously not +possible with Emacs, Emacs separates a frame from a system window. Each system window created (including the initial window created during Emacs startup) is appended to a list of windows that do not @@ -402,15 +402,15 @@ devices. @item The @code{alpha}, @code{alpha-background}, @code{z-group}, -@code{override-redirect}, @code{mouse-color}, @code{cursor-color}, -@code{cursor-type}, @code{title}, @code{wait-for-wm}, @code{sticky}, -@code{undecorated} and @code{tool-bar-position} frame parameters -(@pxref{Frame Parameters,,, elisp, the Emacs Lisp Reference Manual}) -are unsupported. +@code{override-redirect}, @code{mouse-color}, @code{title}, +@code{wait-for-wm}, @code{sticky}, @code{undecorated} and +@code{tool-bar-position} frame parameters (@pxref{Frame Parameters,,, +elisp, the Emacs Lisp Reference Manual}) are unsupported. @item -The @code{fullscreen} frame parameter is always @code{maximized} for -top-level frames. +On Android 4.0 and earlier, the @code{fullscreen} frame parameter is +always @code{maximized} for top-level frames; on later versions of +Android, it can also be @code{fullscreen}. @end itemize @cindex selections, android diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 3156a144a0f..7e09e608984 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -26,12 +26,16 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.os.Build; +import android.os.Bundle; import android.util.Log; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; import android.view.Menu; +import android.view.View; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.widget.FrameLayout.LayoutParams; +import android.widget.FrameLayout; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -56,6 +60,9 @@ public class EmacsActivity extends Activity /* Whether or not this activity is paused. */ private boolean isPaused; + /* Whether or not this activity is fullscreen. */ + private boolean isFullscreen; + static { focusedActivities = new ArrayList (); @@ -104,6 +111,8 @@ public class EmacsActivity extends Activity public void detachWindow () { + syncFullscreenWith (null); + if (window == null) Log.w (TAG, "detachWindow called, but there is no window"); else @@ -131,6 +140,8 @@ public class EmacsActivity extends Activity throw new IllegalStateException ("trying to attach window when one" + " already exists"); + syncFullscreenWith (child); + /* Record and attach the view. */ window = child; @@ -230,6 +241,9 @@ public class EmacsActivity extends Activity public void onWindowFocusChanged (boolean isFocused) { + Log.d (TAG, ("onWindowFocusChanged: " + + (isFocused ? "YES" : "NO"))); + if (isFocused && !focusedActivities.contains (this)) { focusedActivities.add (this); @@ -257,6 +271,9 @@ public class EmacsActivity extends Activity { isPaused = false; + /* Update the window insets. */ + syncFullscreenWith (window); + EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } @@ -279,4 +296,88 @@ public class EmacsActivity extends Activity super.onContextMenuClosed (menu); } + + @SuppressWarnings ("deprecation") + public void + syncFullscreenWith (EmacsWindow emacsWindow) + { + WindowInsetsController controller; + Window window; + int behavior, flags; + View view; + + if (emacsWindow != null) + isFullscreen = emacsWindow.fullscreen; + else + isFullscreen = false; + + /* On Android 11 or later, use the window insets controller to + control whether or not the view is fullscreen. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + { + window = getWindow (); + + /* If there is no attached window, return immediately. */ + if (window == null) + return; + + behavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + controller = window.getInsetsController (); + controller.setSystemBarsBehavior (behavior); + + if (isFullscreen) + controller.hide (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + else + controller.show (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + /* On Android 4.1 or later, use `setSystemUiVisibility'. */ + + window = getWindow (); + + if (window == null) + return; + + view = window.getDecorView (); + + if (isFullscreen) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + /* This flag means that Emacs will be full screen, but + the system will cancel the full screen state upon + switching to another program. */ + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_FULLSCREEN); + else + { + /* These flags means that Emacs will be full screen as + long as the state flag is set. */ + flags = 0; + flags |= View.SYSTEM_UI_FLAG_FULLSCREEN; + flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + view.setSystemUiVisibility (flags); + } + } + else + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + @Override + public void + onAttachedToWindow () + { + super.onAttachedToWindow (); + + /* Update the window insets. */ + syncFullscreenWith (window); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 0eca35cec61..90fc4c44198 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -128,6 +128,9 @@ private class Coordinate events. */ public LinkedHashMap eventStrings; + /* Whether or not this window is fullscreen. */ + public boolean fullscreen; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -1179,4 +1182,27 @@ else if (EmacsWindow.this.isMapped) return any; } + + public void + setFullscreen (final boolean isFullscreen) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsActivity activity; + Object tem; + + fullscreen = isFullscreen; + tem = getAttachedConsumer (); + + if (tem != null) + { + activity = (EmacsActivity) getAttachedConsumer (); + activity.syncFullscreenWith (EmacsWindow.this); + } + } + }); + } }; diff --git a/src/android.c b/src/android.c index d21aaabc1ac..fc5e6d278ed 100644 --- a/src/android.c +++ b/src/android.c @@ -136,6 +136,7 @@ struct android_emacs_window jmethodID swap_buffers; jmethodID toggle_on_screen_keyboard; jmethodID lookup_string; + jmethodID set_fullscreen; }; /* The API level of the current device. */ @@ -1920,6 +1921,7 @@ android_init_emacs_window (void) FIND_METHOD (toggle_on_screen_keyboard, "toggleOnScreenKeyboard", "(Z)V"); FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;"); + FIND_METHOD (set_fullscreen, "setFullscreen", "(Z)V"); #undef FIND_METHOD } @@ -5475,6 +5477,36 @@ android_reset_ic (android_window window, enum android_ic_mode mode) +/* Window decoration management functions. */ + +/* Make the specified WINDOW fullscreen, i.e. obscure all of the + system navigation and status bars. If not FULLSCREEN, make it + maximized instead. + + Value is 1 if the system does not support this, else 0. */ + +int +android_set_fullscreen (android_window window, bool fullscreen) +{ + jobject object; + + /* Android 4.0 and earlier don't support fullscreen windows. */ + + if (android_api_level < 16) + return 1; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallVoidMethod (android_java_env, + object, + window_class.set_fullscreen, + (jboolean) fullscreen); + android_exception_check (); + return 0; +} + + + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/androidgui.h b/src/androidgui.h index 25dc6754fff..84419457a8a 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -614,6 +614,7 @@ extern int android_wc_lookup_string (android_key_pressed_event *, extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t); extern void android_reset_ic (android_window, enum android_ic_mode); +extern int android_set_fullscreen (android_window, bool); #endif diff --git a/src/androidterm.c b/src/androidterm.c index 0fbee1e9867..62a8d5d12b3 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1792,12 +1792,30 @@ android_make_frame_visible_invisible (struct frame *f, bool visible) static void android_fullscreen_hook (struct frame *f) { - /* Explicitly setting fullscreen is not supported on Android. */ + Lisp_Object wanted; if (!FRAME_PARENT_FRAME (f)) - store_frame_param (f, Qfullscreen, Qmaximized); + { + /* Explicitly setting fullscreen is not supported on older + Android versions. */ + + wanted = (f->want_fullscreen == FULLSCREEN_BOTH + ? Qfullscreen : Qmaximized); + + if (android_set_fullscreen (FRAME_ANDROID_WINDOW (f), + EQ (wanted, Qfullscreen))) + store_frame_param (f, Qfullscreen, Qmaximized); + else + store_frame_param (f, Qfullscreen, wanted); + } else - store_frame_param (f, Qfullscreen, Qnil); + { + store_frame_param (f, Qfullscreen, Qnil); + + /* If this is a child frame, don't keep it fullscreen + anymore. */ + android_set_fullscreen (FRAME_ANDROID_WINDOW (f), false); + } } void