Loading core/java/com/android/internal/statusbar/IStatusBarService.aidl +1 −2 Original line number Original line Diff line number Diff line Loading @@ -48,8 +48,7 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); void setIconVisibility(String slot, boolean visible); @UnsupportedAppUsage @UnsupportedAppUsage void removeIcon(String slot); void removeIcon(String slot); // TODO(b/117478341): support back button change when IME is showing on a external display. void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition, void setImeWindowStatus(in IBinder token, int vis, int backDisposition, boolean showImeSwitcher); boolean showImeSwitcher); void expandSettingsPanel(String subPanel); void expandSettingsPanel(String subPanel); Loading packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +38 −4 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,10 @@ package com.android.systemui.statusbar; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_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.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; Loading @@ -40,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.os.Message; import android.util.Pair; import android.util.Pair; import android.util.SparseArray; import android.util.SparseArray; import android.view.inputmethod.InputMethodSystemProperty; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; Loading Loading @@ -127,6 +131,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private Handler mHandler = new H(Looper.getMainLooper()); private Handler mHandler = new H(Looper.getMainLooper()); /** A map of display id - disable flag pair */ /** A map of display id - disable flag pair */ private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>(); 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. * These methods are called back on the main thread. Loading Loading @@ -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 final class H extends Handler { private H(Looper l) { private H(Looper l) { super(l); super(l); Loading Loading @@ -852,10 +887,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; break; case MSG_SHOW_IME_BUTTON: case MSG_SHOW_IME_BUTTON: args = (SomeArgs) msg.obj; args = (SomeArgs) msg.obj; for (int i = 0; i < mCallbacks.size(); i++) { handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */, mCallbacks.get(i).setImeWindowStatus(args.argi1, (IBinder) args.arg1, args.argi2 /* vis */, args.argi3 /* backDisposition */, args.argi2, args.argi3, args.argi4 != 0 /* showImeSwitcher */); args.argi4 != 0 /* showImeSwitcher */); } break; break; case MSG_SHOW_RECENT_APPS: case MSG_SHOW_RECENT_APPS: for (int i = 0; i < mCallbacks.size(); i++) { for (int i = 0; i < mCallbacks.size(); i++) { Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -1063,4 +1063,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback context.getSystemService(WindowManager.class).addView(navigationBarView, lp); context.getSystemService(WindowManager.class).addView(navigationBarView, lp); return navigationBarView; return navigationBarView; } } @VisibleForTesting int getNavigationIconHints() { return mNavigationIconHints; } } } packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +165 −5 Original line number Original line Diff line number Diff line Loading @@ -14,18 +14,38 @@ package com.android.systemui.statusbar.phone; 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.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import android.annotation.LayoutRes; import android.annotation.Nullable; import android.app.Fragment; import android.app.Fragment; import android.app.FragmentController; import android.app.FragmentHostCallback; import android.content.Context; import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner; import android.testing.LeakCheck.Tracker; import android.testing.LeakCheck.Tracker; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; 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.WindowManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; Loading @@ -34,6 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.Dependency; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiTestableContext; import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.recents.Recents; Loading @@ -50,9 +71,16 @@ import org.junit.runner.RunWith; @RunWithLooper() @RunWithLooper() @SmallTest @SmallTest public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { 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 = private OverviewProxyService mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class); mDependency.injectMockDependency(OverviewProxyService.class); private CommandQueue mCommandQueue; private AccessibilityManagerWrapper mAccessibilityWrapper = private AccessibilityManagerWrapper mAccessibilityWrapper = new AccessibilityManagerWrapper(mContext) { new AccessibilityManagerWrapper(mContext) { Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); Loading @@ -73,15 +101,51 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { } } protected void createRootView() { protected void createRootView() { mView = new NavigationBarFrame(mContext); mView = new NavigationBarFrame(mSysuiContext); mView.setId(NAV_BAR_VIEW_ID); } } @Before @Before public void setup() { public void setupFragment() throws Exception { mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); 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(StatusBar.class, mock(StatusBar.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Divider.class, mock(Divider.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); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); WindowManager windowManager = mock(WindowManager.class); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); Loading @@ -102,15 +166,111 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { navigationBarFragment.onHomeLongClick(navigationBarFragment.getView()); 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 @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { protected Fragment instantiate(Context context, String className, Bundle arguments) { DeviceProvisionedController deviceProvisionedController = DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); mock(DeviceProvisionedController.class); assertNotNull(mAccessibilityWrapper); assertNotNull(mAccessibilityWrapper); return new NavigationBarFragment(mAccessibilityWrapper, return new NavigationBarFragment( context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper : mock(AccessibilityManagerWrapper.class), deviceProvisionedController, deviceProvisionedController, new MetricsLogger(), new MetricsLogger(), new AssistManager(deviceProvisionedController, mContext), mock(AssistManager.class), mOverviewProxyService); 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; } } } } services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +28 −8 Original line number Original line Diff line number Diff line Loading @@ -575,6 +575,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ */ int mCurTokenDisplayId = INVALID_DISPLAY; 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; final ImeDisplayValidator mImeDisplayValidator; /** /** Loading Loading @@ -625,7 +631,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * currently invisible. * currently invisible. * </dd> * </dd> * </dl> * </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; int mImeWindowVis; Loading Loading @@ -2124,12 +2131,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ */ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { // We always assume that the default display id suitable to show the IME window. return FALLBACK_DISPLAY_ID; return DEFAULT_DISPLAY; } } // Show IME in default display when the display with IME target doesn't support system // Show IME window on fallback display when the display is not allowed. // decorations. return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID; return checker.displayCanShowIme(displayId) ? displayId : DEFAULT_DISPLAY; } } @Override @Override Loading Loading @@ -2198,6 +2203,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); } catch (RemoteException e) { } 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; mCurToken = null; mCurTokenDisplayId = INVALID_DISPLAY; mCurTokenDisplayId = INVALID_DISPLAY; } } Loading Loading @@ -2399,10 +2408,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @BinderThread @SuppressWarnings("deprecation") @SuppressWarnings("deprecation") private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); synchronized (mMethodMap) { synchronized (mMethodMap) { if (!calledWithValidTokenLocked(token)) { if (!calledWithValidTokenLocked(token)) { return; 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; mImeWindowVis = vis; mBackDisposition = backDisposition; mBackDisposition = backDisposition; updateSystemUiLocked(vis, backDisposition); updateSystemUiLocked(vis, backDisposition); Loading Loading @@ -2447,7 +2466,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) { if (DEBUG) { Slog.d(TAG, "IME window vis: " + vis Slog.d(TAG, "IME window vis: " + vis + " active: " + (vis & InputMethodService.IME_ACTIVE) + " 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 // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure Loading @@ -2461,7 +2481,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { if (mStatusBar != null) { mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition, mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition, needsToShowImeSwitcher); needsToShowImeSwitcher); } } final InputMethodInfo imi = mMethodMap.get(mCurMethodId); final InputMethodInfo imi = mMethodMap.get(mCurMethodId); Loading Loading
core/java/com/android/internal/statusbar/IStatusBarService.aidl +1 −2 Original line number Original line Diff line number Diff line Loading @@ -48,8 +48,7 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); void setIconVisibility(String slot, boolean visible); @UnsupportedAppUsage @UnsupportedAppUsage void removeIcon(String slot); void removeIcon(String slot); // TODO(b/117478341): support back button change when IME is showing on a external display. void setImeWindowStatus(int displayId, in IBinder token, int vis, int backDisposition, void setImeWindowStatus(in IBinder token, int vis, int backDisposition, boolean showImeSwitcher); boolean showImeSwitcher); void expandSettingsPanel(String subPanel); void expandSettingsPanel(String subPanel); Loading
packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +38 −4 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,10 @@ package com.android.systemui.statusbar; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_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.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; Loading @@ -40,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.os.Message; import android.util.Pair; import android.util.Pair; import android.util.SparseArray; import android.util.SparseArray; import android.view.inputmethod.InputMethodSystemProperty; import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting; Loading Loading @@ -127,6 +131,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private Handler mHandler = new H(Looper.getMainLooper()); private Handler mHandler = new H(Looper.getMainLooper()); /** A map of display id - disable flag pair */ /** A map of display id - disable flag pair */ private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>(); 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. * These methods are called back on the main thread. Loading Loading @@ -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 final class H extends Handler { private H(Looper l) { private H(Looper l) { super(l); super(l); Loading Loading @@ -852,10 +887,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; break; case MSG_SHOW_IME_BUTTON: case MSG_SHOW_IME_BUTTON: args = (SomeArgs) msg.obj; args = (SomeArgs) msg.obj; for (int i = 0; i < mCallbacks.size(); i++) { handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */, mCallbacks.get(i).setImeWindowStatus(args.argi1, (IBinder) args.arg1, args.argi2 /* vis */, args.argi3 /* backDisposition */, args.argi2, args.argi3, args.argi4 != 0 /* showImeSwitcher */); args.argi4 != 0 /* showImeSwitcher */); } break; break; case MSG_SHOW_RECENT_APPS: case MSG_SHOW_RECENT_APPS: for (int i = 0; i < mCallbacks.size(); i++) { for (int i = 0; i < mCallbacks.size(); i++) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -1063,4 +1063,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback context.getSystemService(WindowManager.class).addView(navigationBarView, lp); context.getSystemService(WindowManager.class).addView(navigationBarView, lp); return navigationBarView; return navigationBarView; } } @VisibleForTesting int getNavigationIconHints() { return mNavigationIconHints; } } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +165 −5 Original line number Original line Diff line number Diff line Loading @@ -14,18 +14,38 @@ package com.android.systemui.statusbar.phone; 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.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; import android.annotation.LayoutRes; import android.annotation.Nullable; import android.app.Fragment; import android.app.Fragment; import android.app.FragmentController; import android.app.FragmentHostCallback; import android.content.Context; import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner; import android.testing.LeakCheck.Tracker; import android.testing.LeakCheck.Tracker; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; 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.WindowManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; Loading @@ -34,6 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.Dependency; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiTestableContext; import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.recents.Recents; Loading @@ -50,9 +71,16 @@ import org.junit.runner.RunWith; @RunWithLooper() @RunWithLooper() @SmallTest @SmallTest public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { 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 = private OverviewProxyService mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class); mDependency.injectMockDependency(OverviewProxyService.class); private CommandQueue mCommandQueue; private AccessibilityManagerWrapper mAccessibilityWrapper = private AccessibilityManagerWrapper mAccessibilityWrapper = new AccessibilityManagerWrapper(mContext) { new AccessibilityManagerWrapper(mContext) { Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); Loading @@ -73,15 +101,51 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { } } protected void createRootView() { protected void createRootView() { mView = new NavigationBarFrame(mContext); mView = new NavigationBarFrame(mSysuiContext); mView.setId(NAV_BAR_VIEW_ID); } } @Before @Before public void setup() { public void setupFragment() throws Exception { mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); 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(StatusBar.class, mock(StatusBar.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Divider.class, mock(Divider.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); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); WindowManager windowManager = mock(WindowManager.class); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); Loading @@ -102,15 +166,111 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { navigationBarFragment.onHomeLongClick(navigationBarFragment.getView()); 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 @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { protected Fragment instantiate(Context context, String className, Bundle arguments) { DeviceProvisionedController deviceProvisionedController = DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); mock(DeviceProvisionedController.class); assertNotNull(mAccessibilityWrapper); assertNotNull(mAccessibilityWrapper); return new NavigationBarFragment(mAccessibilityWrapper, return new NavigationBarFragment( context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper : mock(AccessibilityManagerWrapper.class), deviceProvisionedController, deviceProvisionedController, new MetricsLogger(), new MetricsLogger(), new AssistManager(deviceProvisionedController, mContext), mock(AssistManager.class), mOverviewProxyService); 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; } } } }
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +28 −8 Original line number Original line Diff line number Diff line Loading @@ -575,6 +575,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ */ int mCurTokenDisplayId = INVALID_DISPLAY; 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; final ImeDisplayValidator mImeDisplayValidator; /** /** Loading Loading @@ -625,7 +631,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * currently invisible. * currently invisible. * </dd> * </dd> * </dl> * </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; int mImeWindowVis; Loading Loading @@ -2124,12 +2131,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ */ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { // We always assume that the default display id suitable to show the IME window. return FALLBACK_DISPLAY_ID; return DEFAULT_DISPLAY; } } // Show IME in default display when the display with IME target doesn't support system // Show IME window on fallback display when the display is not allowed. // decorations. return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID; return checker.displayCanShowIme(displayId) ? displayId : DEFAULT_DISPLAY; } } @Override @Override Loading Loading @@ -2198,6 +2203,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); } catch (RemoteException e) { } 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; mCurToken = null; mCurTokenDisplayId = INVALID_DISPLAY; mCurTokenDisplayId = INVALID_DISPLAY; } } Loading Loading @@ -2399,10 +2408,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @BinderThread @SuppressWarnings("deprecation") @SuppressWarnings("deprecation") private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); synchronized (mMethodMap) { synchronized (mMethodMap) { if (!calledWithValidTokenLocked(token)) { if (!calledWithValidTokenLocked(token)) { return; 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; mImeWindowVis = vis; mBackDisposition = backDisposition; mBackDisposition = backDisposition; updateSystemUiLocked(vis, backDisposition); updateSystemUiLocked(vis, backDisposition); Loading Loading @@ -2447,7 +2466,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) { if (DEBUG) { Slog.d(TAG, "IME window vis: " + vis Slog.d(TAG, "IME window vis: " + vis + " active: " + (vis & InputMethodService.IME_ACTIVE) + " 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 // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure Loading @@ -2461,7 +2481,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { if (mStatusBar != null) { mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition, mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition, needsToShowImeSwitcher); needsToShowImeSwitcher); } } final InputMethodInfo imi = mMethodMap.get(mCurMethodId); final InputMethodInfo imi = mMethodMap.get(mCurMethodId); Loading