diff options
9 files changed, 254 insertions, 30 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 5fee2c9bbdd3..8f8e4d8dc108 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -48,8 +48,7 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); @UnsupportedAppUsage void removeIcon(String slot); - // TODO(b/117478341): support back button change when IME is showing on a external display. - void setImeWindowStatus(in IBinder token, int vis, int backDisposition, + void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition, boolean showImeSwitcher); void expandSettingsPanel(String subPanel); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 2f99cf311eec..d5849598342f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -18,7 +18,10 @@ package com.android.systemui.statusbar; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; @@ -40,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.util.Pair; import android.util.SparseArray; +import android.view.inputmethod.InputMethodSystemProperty; import androidx.annotation.VisibleForTesting; @@ -127,6 +131,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private Handler mHandler = new H(Looper.getMainLooper()); /** A map of display id - disable flag pair */ private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>(); + /** + * The last ID of the display where IME window for which we received setImeWindowStatus + * event. + */ + private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; /** * These methods are called back on the main thread. @@ -785,6 +794,32 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } + private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { + if (displayId == INVALID_DISPLAY) return; + + if (!InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED + && mLastUpdatedImeDisplayId != displayId + && mLastUpdatedImeDisplayId != INVALID_DISPLAY) { + // Set previous NavBar's IME window status as invisible when IME + // window switched to another display for single-session IME case. + sendImeInvisibleStatusForPrevNavBar(); + } + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setImeWindowStatus(displayId, token, vis, backDisposition, + showImeSwitcher); + } + mLastUpdatedImeDisplayId = displayId; + } + + private void sendImeInvisibleStatusForPrevNavBar() { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, + null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, + false /* showImeSwitcher */); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -852,10 +887,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; case MSG_SHOW_IME_BUTTON: args = (SomeArgs) msg.obj; - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).setImeWindowStatus(args.argi1, (IBinder) args.arg1, - args.argi2, args.argi3, args.argi4 != 0 /* showImeSwitcher */); - } + handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */, + args.argi2 /* vis */, args.argi3 /* backDisposition */, + args.argi4 != 0 /* showImeSwitcher */); break; case MSG_SHOW_RECENT_APPS: for (int i = 0; i < mCallbacks.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 591b1b48ddc2..083b3dfb9b65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -1064,4 +1064,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback context.getSystemService(WindowManager.class).addView(navigationBarView, lp); return navigationBarView; } + + @VisibleForTesting + int getNavigationIconHints() { + return mNavigationIconHints; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 6a0d61dc13c9..3ae57e391005 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -14,18 +14,38 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_VISIBLE; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.LayoutRes; +import android.annotation.Nullable; import android.app.Fragment; +import android.app.FragmentController; +import android.app.FragmentHostCallback; import android.content.Context; +import android.hardware.display.DisplayManagerGlobal; import android.os.Bundle; +import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.LeakCheck.Tracker; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; +import android.view.DisplayInfo; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; @@ -34,6 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.SysuiTestableContext; import com.android.systemui.assist.AssistManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; @@ -50,9 +71,16 @@ import org.junit.runner.RunWith; @RunWithLooper() @SmallTest public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { + private static final int EXTERNAL_DISPLAY_ID = 2; + private static final int NAV_BAR_VIEW_ID = 43; + + private Fragment mFragmentExternalDisplay; + private FragmentController mControllerExternalDisplay; + private SysuiTestableContext mSysuiTestableContextExternal; private OverviewProxyService mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class); + private CommandQueue mCommandQueue; private AccessibilityManagerWrapper mAccessibilityWrapper = new AccessibilityManagerWrapper(mContext) { Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); @@ -73,15 +101,51 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { } protected void createRootView() { - mView = new NavigationBarFrame(mContext); + mView = new NavigationBarFrame(mSysuiContext); + mView.setId(NAV_BAR_VIEW_ID); } @Before - public void setup() { - mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + public void setupFragment() throws Exception { + setupSysuiDependency(); + createRootView(); + TestableLooper.get(this).runWithLooper(() -> { + mHandler = new Handler(); + + mFragment = instantiate(mSysuiContext, NavigationBarFragment.class.getName(), null); + mFragments = FragmentController.createController( + new HostCallbacksForExternalDisplay(mSysuiContext)); + mFragments.attachHost(null); + mFragments.getFragmentManager().beginTransaction() + .replace(NAV_BAR_VIEW_ID, mFragment) + .commit(); + mControllerExternalDisplay = FragmentController.createController( + new HostCallbacksForExternalDisplay(mSysuiTestableContextExternal)); + mControllerExternalDisplay.attachHost(null); + mFragmentExternalDisplay = instantiate(mSysuiTestableContextExternal, + NavigationBarFragment.class.getName(), null); + mControllerExternalDisplay.getFragmentManager().beginTransaction() + .replace(NAV_BAR_VIEW_ID, mFragmentExternalDisplay) + .commit(); + }); + } + + private void setupSysuiDependency() { + mCommandQueue = new CommandQueue(mContext); + mSysuiContext.putComponent(CommandQueue.class, mCommandQueue); mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Divider.class, mock(Divider.class)); + + Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID, + new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); + mSysuiTestableContextExternal = (SysuiTestableContext) mSysuiContext.createDisplayContext( + display); + mSysuiTestableContextExternal.putComponent(CommandQueue.class, mCommandQueue); + mSysuiTestableContextExternal.putComponent(StatusBar.class, mock(StatusBar.class)); + mSysuiTestableContextExternal.putComponent(Recents.class, mock(Recents.class)); + mSysuiTestableContextExternal.putComponent(Divider.class, mock(Divider.class)); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); @@ -102,15 +166,111 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { navigationBarFragment.onHomeLongClick(navigationBarFragment.getView()); } + @Test + public void testSetImeWindowStatusWhenImeSwitchOnDisplay() { + // Create default & external NavBar fragment. + NavigationBarFragment defaultNavBar = (NavigationBarFragment) mFragment; + NavigationBarFragment externalNavBar = (NavigationBarFragment) mFragmentExternalDisplay; + mFragments.dispatchCreate(); + processAllMessages(); + mFragments.dispatchResume(); + processAllMessages(); + mControllerExternalDisplay.dispatchCreate(); + processAllMessages(); + mControllerExternalDisplay.dispatchResume(); + processAllMessages(); + + // Set IME window status for default NavBar. + mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); + Handler.getMain().runWithScissors(() -> { }, 500); + + // Verify IME window state will be updated in default NavBar & external NavBar state reset. + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, + defaultNavBar.getNavigationIconHints()); + assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); + assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + + // Set IME window status for external NavBar. + mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, + IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); + Handler.getMain().runWithScissors(() -> { }, 500); + + // Verify IME window state will be updated in external NavBar & default NavBar state reset. + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, + externalNavBar.getNavigationIconHints()); + assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); + assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + } + @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); assertNotNull(mAccessibilityWrapper); - return new NavigationBarFragment(mAccessibilityWrapper, + return new NavigationBarFragment( + context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper + : mock(AccessibilityManagerWrapper.class), deviceProvisionedController, new MetricsLogger(), - new AssistManager(deviceProvisionedController, mContext), + mock(AssistManager.class), mOverviewProxyService); } + + private class HostCallbacksForExternalDisplay extends + FragmentHostCallback<NavigationBarFragmentTest> { + private Context mDisplayContext; + + HostCallbacksForExternalDisplay(Context context) { + super(context, mHandler, 0); + mDisplayContext = context; + } + + @Override + public NavigationBarFragmentTest onGetHost() { + return NavigationBarFragmentTest.this; + } + + @Override + public Fragment instantiate(Context context, String className, Bundle arguments) { + return NavigationBarFragmentTest.this.instantiate(context, className, arguments); + } + + @Override + public View onFindViewById(int id) { + return mView.findViewById(id); + } + + @Override + public LayoutInflater onGetLayoutInflater() { + return new LayoutInflaterWrapper(mDisplayContext); + } + } + + private static class LayoutInflaterWrapper extends LayoutInflater { + protected LayoutInflaterWrapper(Context context) { + super(context); + } + + @Override + public LayoutInflater cloneInContext(Context newContext) { + return null; + } + + @Override + public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, + boolean attachToRoot) { + NavigationBarView view = mock(NavigationBarView.class); + when(view.getDisplay()).thenReturn(mContext.getDisplay()); + when(view.getBackButton()).thenReturn(mock(ButtonDispatcher.class)); + when(view.getHomeButton()).thenReturn(mock(ButtonDispatcher.class)); + when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class)); + when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class)); + when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class)); + when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class)); + when(view.getLightTransitionsController()).thenReturn( + mock(LightBarTransitionsController.class)); + return view; + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e88d62f58507..de2fd42d398c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -575,6 +575,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ int mCurTokenDisplayId = INVALID_DISPLAY; + /** + * The display ID of the input method indicates the fallback display which returned by + * {@link #computeImeDisplayIdForTarget}. + */ + private static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY; + final ImeDisplayValidator mImeDisplayValidator; /** @@ -625,7 +631,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * currently invisible. * </dd> * </dl> - * <em>Do not update this value outside of setImeWindowStatus.</em> + * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and + * {@link #unbindCurrentMethodLocked()}.</em> */ int mImeWindowVis; @@ -2124,12 +2131,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { - // We always assume that the default display id suitable to show the IME window. - return DEFAULT_DISPLAY; + return FALLBACK_DISPLAY_ID; } - // Show IME in default display when the display with IME target doesn't support system - // decorations. - return checker.displayCanShowIme(displayId) ? displayId : DEFAULT_DISPLAY; + // Show IME window on fallback display when the display is not allowed. + return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID; } @Override @@ -2198,6 +2203,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); } catch (RemoteException e) { } + // Set IME window status as invisible when unbind current method. + mImeWindowVis = 0; + mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; + updateSystemUiLocked(mImeWindowVis, mBackDisposition); mCurToken = null; mCurTokenDisplayId = INVALID_DISPLAY; } @@ -2399,10 +2408,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @SuppressWarnings("deprecation") private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { + final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); + synchronized (mMethodMap) { if (!calledWithValidTokenLocked(token)) { return; } + // Skip update IME status when current token display is not same as focused display. + // Note that we still need to update IME status when focusing external display + // that does not support system decoration and fallback to show IME on default + // display since it is intentional behavior. + if (mCurTokenDisplayId != topFocusedDisplayId + && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) { + return; + } mImeWindowVis = vis; mBackDisposition = backDisposition; updateSystemUiLocked(vis, backDisposition); @@ -2447,7 +2466,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) { Slog.d(TAG, "IME window vis: " + vis + " active: " + (vis & InputMethodService.IME_ACTIVE) - + " inv: " + (vis & InputMethodService.IME_INVISIBLE)); + + " inv: " + (vis & InputMethodService.IME_INVISIBLE) + + " displayId: " + mCurTokenDisplayId); } // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure @@ -2461,7 +2481,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { - mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition, + mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition, needsToShowImeSwitcher); } final InputMethodInfo imi = mMethodMap.get(mCurMethodId); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index aaf3df39d429..9cbf00b85e8b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -843,10 +843,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } - // TODO(b/117478341): support back button change when IME is showing on a external display. @Override - public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition, - final boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, final IBinder token, final int vis, + final int backDisposition, final boolean showImeSwitcher) { enforceStatusBar(); if (SPEW) { @@ -857,18 +856,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // In case of IME change, we need to call up setImeWindowStatus() regardless of // mImeWindowVis because mImeWindowVis may not have been set to false when the // previous IME was destroyed. - // TODO(b/117478341): support back button change when IME is showing on a external - // display. - getUiState(DEFAULT_DISPLAY) - .setImeWindowState(vis, backDisposition, showImeSwitcher, token); + getUiState(displayId).setImeWindowState(vis, backDisposition, showImeSwitcher, token); mHandler.post(() -> { if (mBar == null) return; try { - // TODO(b/117478341): support back button change when IME is showing on a - // external display. mBar.setImeWindowStatus( - DEFAULT_DISPLAY, token, vis, backDisposition, showImeSwitcher); + displayId, token, vis, backDisposition, showImeSwitcher); } catch (RemoteException ex) { } }); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9d80425435ef..78c5dbddee41 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -476,6 +476,11 @@ public abstract class WindowManagerInternal { public abstract int getDisplayIdForWindow(IBinder windowToken); /** + * @return The top focused display ID. + */ + public abstract int getTopFocusedDisplayId(); + + /** * Checks whether this display should support showing system decorations. */ public abstract boolean shouldShowSystemDecorOnDisplay(int displayId); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ce496f47be54..0215b7556215 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7340,6 +7340,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public int getTopFocusedDisplayId() { + synchronized (mGlobalLock) { + return mRoot.getTopFocusedDisplayContent().getDisplayId(); + } + } + + @Override public boolean shouldShowSystemDecorOnDisplay(int displayId) { synchronized (mGlobalLock) { return WindowManagerService.this.shouldShowSystemDecors(displayId); diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 9f60cce61bce..6cd88b51cb82 100644 --- a/tests/testables/src/android/testing/BaseFragmentTest.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -52,7 +52,7 @@ public abstract class BaseFragmentTest { private static final int VIEW_ID = 42; private final Class<? extends Fragment> mCls; - private Handler mHandler; + protected Handler mHandler; protected FrameLayout mView; protected FragmentController mFragments; protected Fragment mFragment; |