From 8d73060186e11df0857f11428d54f67bce85a850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cosmin=20B=C4=83ie=C8=99?= Date: Fri, 11 Aug 2023 18:34:42 +0200 Subject: [PATCH] Allow hiding the IME navigation bar This implements a mechanism to allow IMEs to request hiding the IME navigation bar (containing the IME collapse and IME switcher buttons) using existing InsetsControllers API. To make this work, the previous insetsSizeOverride for the IME set on the system navigation bar was removed, and the IME navigation bar is now treated as a caption bar internally, providing insets from its own process. Test: atest InputMethodServiceTest#testRequestHideImeCaptionBar InputMethodServiceTest#testRequestHideThenShowImeCaptionBar Bug: 292515843 Change-Id: I3d367ea4eec90e980853d7de29907020d24d2e11 --- .../InputMethodService.java | 6 +++ .../NavigationBarController.java | 34 ++++++++++++- core/java/android/view/InsetsController.java | 48 ++++++++++++++++++- core/java/android/view/InsetsSource.java | 8 +++- .../android/view/PendingInsetsController.java | 9 ++++ .../android/view/WindowInsetsController.java | 9 ++++ .../systemui/navigationbar/NavigationBar.java | 11 +++-- .../InputMethodManagerService.java | 2 + 8 files changed, 120 insertions(+), 7 deletions(-) diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 795eb4a737efb..a04c6bb98245c 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -576,6 +576,12 @@ public class InputMethodService extends AbstractInputMethodService { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L; + /** + * Enable the logic to allow hiding the IME caption bar ("fake" IME navigation bar). + * @hide + */ + public static final boolean ENABLE_HIDE_IME_CAPTION_BAR = false; + LayoutInflater mInflater; TypedArray mThemeAttrs; @UnsupportedAppUsage diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 78388efe98c7a..c01664e557445 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -16,6 +16,8 @@ package android.inputmethodservice; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; +import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import android.animation.ValueAnimator; @@ -230,6 +232,16 @@ final class NavigationBarController { setIconTintInternal(calculateTargetDarkIntensity(mAppearance, mDrawLegacyNavigationBarBackground)); + + if (ENABLE_HIDE_IME_CAPTION_BAR) { + mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> { + if (mNavigationBarFrame != null) { + boolean visible = insets.isVisible(captionBar()); + mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + } + return view.onApplyWindowInsets(insets); + }); + } } private void uninstallNavigationBarFrameIfNecessary() { @@ -240,6 +252,9 @@ final class NavigationBarController { if (parent instanceof ViewGroup) { ((ViewGroup) parent).removeView(mNavigationBarFrame); } + if (ENABLE_HIDE_IME_CAPTION_BAR) { + mNavigationBarFrame.setOnApplyWindowInsetsListener(null); + } mNavigationBarFrame = null; } @@ -414,7 +429,9 @@ final class NavigationBarController { decor.bringChildToFront(mNavigationBarFrame); } } - mNavigationBarFrame.setVisibility(View.VISIBLE); + if (!ENABLE_HIDE_IME_CAPTION_BAR) { + mNavigationBarFrame.setVisibility(View.VISIBLE); + } } } @@ -435,6 +452,11 @@ final class NavigationBarController { mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + if (ENABLE_HIDE_IME_CAPTION_BAR) { + mService.mWindow.getWindow().getDecorView().getWindowInsetsController() + .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight()); + } + if (imeDrawsImeNavBar) { installNavigationBarFrameIfNecessary(); if (mNavigationBarFrame == null) { @@ -528,6 +550,16 @@ final class NavigationBarController { return drawLegacyNavigationBarBackground; } + /** + * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead. + */ + private int getImeCaptionBarHeight() { + return mImeDrawsImeNavBar + ? mService.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_frame_height) + : 0; + } + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 4ecfc4044b1df..c6d8bd18bc280 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,10 +16,12 @@ package android.view; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.os.Trace.TRACE_TAG_VIEW; import static android.view.InsetsControllerProto.CONTROL; import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsSource.ID_IME; +import static android.view.InsetsSource.ID_IME_CAPTION_BAR; import static android.view.ViewRootImpl.CAPTION_ON_SHELL; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; @@ -40,6 +42,7 @@ import android.app.ActivityThread; import android.content.Context; import android.content.res.CompatibilityInfo; import android.graphics.Insets; +import android.graphics.Point; import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; @@ -652,6 +655,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mLastWindowingMode; private boolean mStartingAnimation; private int mCaptionInsetsHeight = 0; + private int mImeCaptionBarInsetsHeight = 0; private boolean mAnimationsDisabled; private boolean mCompatSysUiVisibilityStaled; @@ -693,6 +697,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) { return; } + if (source1.getId() == ID_IME_CAPTION_BAR) { + return; + } // Don't change the indexes of the sources while traversing. Remove it later. mPendingRemoveIndexes.add(index1); @@ -823,6 +830,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (mFrame.equals(frame)) { return; } + if (mImeCaptionBarInsetsHeight != 0) { + setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight); + } mHost.notifyInsetsChanged(); mFrame.set(frame); } @@ -1007,6 +1017,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (consumer.getId() == ID_IME_CAPTION_BAR) { + // The inset control for the IME caption bar will never be dispatched + // by the server. + continue; + } + final InsetsSourceControl control = mTmpControlArray.get(consumer.getId()); if (control != null) { controllableTypes |= control.getType(); @@ -1499,7 +1515,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation continue; } final InsetsSourceControl control = consumer.getControl(); - if (control != null && control.getLeash() != null) { + if (control != null + && (control.getLeash() != null || control.getId() == ID_IME_CAPTION_BAR)) { controls.put(control.getId(), new InsetsSourceControl(control)); typesReady |= consumer.getType(); } @@ -1884,6 +1901,35 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } + @Override + public void setImeCaptionBarInsetsHeight(int height) { + if (!ENABLE_HIDE_IME_CAPTION_BAR) { + return; + } + Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom); + InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR); + if (mImeCaptionBarInsetsHeight != height + || (source != null && !newFrame.equals(source.getFrame()))) { + mImeCaptionBarInsetsHeight = height; + if (mImeCaptionBarInsetsHeight != 0) { + mState.getOrCreateSource(ID_IME_CAPTION_BAR, captionBar()) + .setFrame(newFrame); + getSourceConsumer(ID_IME_CAPTION_BAR, captionBar()).setControl( + new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(), + null /* leash */, false /* initialVisible */, + new Point(), Insets.NONE), + new int[1], new int[1]); + } else { + mState.removeSource(ID_IME_CAPTION_BAR); + InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR); + if (sourceConsumer != null) { + sourceConsumer.setControl(null, new int[1], new int[1]); + } + } + mHost.notifyInsetsChanged(); + } + } + @Override public void setSystemBarsBehavior(@Behavior int behavior) { mHost.setSystemBarsBehavior(behavior); diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 64411866f020a..60a67de32f886 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -20,6 +20,7 @@ import static android.view.InsetsSourceProto.FRAME; import static android.view.InsetsSourceProto.TYPE; import static android.view.InsetsSourceProto.VISIBLE; import static android.view.InsetsSourceProto.VISIBLE_FRAME; +import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.ime; import android.annotation.IntDef; @@ -47,6 +48,9 @@ public class InsetsSource implements Parcelable { /** The insets source ID of IME */ public static final int ID_IME = createId(null, 0, ime()); + /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */ + static final int ID_IME_CAPTION_BAR = + InsetsSource.createId(null /* owner */, 1 /* index */, captionBar()); /** * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't @@ -215,7 +219,9 @@ public class InsetsSource implements Parcelable { // During drag-move and drag-resizing, the caption insets position may not get updated // before the app frame get updated. To layout the app content correctly during drag events, // we always return the insets with the corresponding height covering the top. - if (getType() == WindowInsets.Type.captionBar()) { + // However, with the "fake" IME navigation bar treated as a caption bar, we skip this case + // to set the actual insets towards the bottom of the screen. + if (getType() == WindowInsets.Type.captionBar() && getId() != ID_IME_CAPTION_BAR) { return Insets.of(0, frame.height(), 0, 0); } // Checks for whether there is shared edge with insets for 0-width/height window. diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index e8f62fc0963f4..a4cbc52416b32 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController { private ArrayList mControllableInsetsChangedListeners = new ArrayList<>(); private int mCaptionInsetsHeight = 0; + private int mImeCaptionBarInsetsHeight = 0; private WindowInsetsAnimationControlListener mLoggingListener; private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); @@ -90,6 +91,11 @@ public class PendingInsetsController implements WindowInsetsController { mCaptionInsetsHeight = height; } + @Override + public void setImeCaptionBarInsetsHeight(int height) { + mImeCaptionBarInsetsHeight = height; + } + @Override public void setSystemBarsBehavior(int behavior) { if (mReplayedInsetsController != null) { @@ -168,6 +174,9 @@ public class PendingInsetsController implements WindowInsetsController { if (mCaptionInsetsHeight != 0) { controller.setCaptionInsetsHeight(mCaptionInsetsHeight); } + if (mImeCaptionBarInsetsHeight != 0) { + controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight); + } if (mAnimationsDisabled) { controller.setAnimationsDisabled(true); } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index bc0bab7b5e953..9028a188f9d1c 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -249,6 +249,15 @@ public interface WindowInsetsController { */ void setCaptionInsetsHeight(int height); + /** + * Sets the insets height for the IME caption bar, which corresponds to the + * "fake" IME navigation bar. + * + * @param height the insets height of the IME caption bar. + * @hide + */ + void setImeCaptionBarInsetsHeight(int height); + /** * Controls the behavior of system bars. * diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index e134f7c10b9b4..ae0ab8423a992 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -25,6 +25,7 @@ import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; @@ -1714,10 +1715,12 @@ public class NavigationBar extends ViewController implements private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) { final InsetsFrameProvider navBarProvider = - new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars()) - .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] { - new InsetsFrameProvider.InsetsSizeOverride( - TYPE_INPUT_METHOD, null)}); + new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars()); + if (!ENABLE_HIDE_IME_CAPTION_BAR) { + navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] { + new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null) + }); + } if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) { navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight)); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 2a617c529f385..6509126dcc601 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -6132,6 +6132,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.dump(pw); p.println(" mInFullscreenMode=" + mInFullscreenMode); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); + p.println(" ENABLE_HIDE_IME_CAPTION_BAR=" + + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR); p.println(" mSettingsObserver=" + mSettingsObserver); p.println(" mStylusIds=" + (mStylusIds != null ? Arrays.toString(mStylusIds.toArray()) : ""));