Loading services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java +16 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,18 @@ class DeferredKeyActionExecutor { getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable(); } /** * Clears all the queued action for given key code. * * @param keyCode the key code whose queued actions will be cleared. */ public void cancelQueuedAction(int keyCode) { TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode); if (actionsBuffer != null) { actionsBuffer.clear(); } } private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) { TimedActionsBuffer buffer = mBuffers.get(keyCode); if (buffer == null || buffer.getDownTime() != downTime) { Loading Loading @@ -146,6 +158,10 @@ class DeferredKeyActionExecutor { mActions.clear(); } void clear() { mActions.clear(); } void dump(String prefix, PrintWriter pw) { if (mExecutable) { pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable"); Loading services/core/java/com/android/server/policy/PhoneWindowManager.java +71 −8 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUiModeManager; Loading Loading @@ -584,6 +585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mLongPressOnStemPrimaryBehavior; private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp; // The focused task at the time when the first STEM_PRIMARY key was released. This can only // be accessed from the looper thread. private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp; private boolean mHandleVolumeKeysInWM; private boolean mPendingKeyguardOccluded; Loading Loading @@ -2135,12 +2140,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static class Injector { private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; private final Looper mLooper; Injector(Context context, WindowManagerFuncs funcs, Looper looper) { Injector(Context context, WindowManagerFuncs funcs) { mContext = context; mWindowManagerFuncs = funcs; mLooper = looper; } Context getContext() { Loading @@ -2152,7 +2155,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } Looper getLooper() { return mLooper; return Looper.myLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( Loading Loading @@ -2195,7 +2198,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void init(Context context, WindowManagerFuncs funcs) { init(new Injector(context, funcs, Looper.myLooper())); init(new Injector(context, funcs)); } @VisibleForTesting Loading Loading @@ -2723,7 +2726,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onPress(long downTime, int unusedDisplayId) { if (mShouldEarlyShortPressOnStemPrimary) { if (shouldHandleStemPrimaryEarlyShortPress()) { return; } // Short-press should be triggered only if app doesn't handle it. Loading @@ -2747,6 +2750,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (count == 3 && mTriplePressOnStemPrimaryBehavior == TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) { // Cancel any queued actions for current key code to prevent them from being // launched after a11y layer enabled. If the action happens early, // undoEarlySinglePress will make sure the correct task is on top. mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); undoEarlySinglePress(); stemPrimaryPress(count); } else { // Other multi-press gestures should be triggered only if app doesn't handle it. Loading @@ -2755,6 +2763,27 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } /** * This method undo the previously launched early-single-press action by bringing the * focused task before launching early-single-press back to top. */ private void undoEarlySinglePress() { if (shouldHandleStemPrimaryEarlyShortPress() && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) { try { mActivityManagerService.startActivityFromRecents( mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null); } catch (RemoteException | IllegalArgumentException e) { Slog.e( TAG, "Failed to start task " + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId + " from recents", e); } } } @Override void onKeyUp(long eventTime, int count, int unusedDisplayId) { if (count == 1) { Loading @@ -2763,14 +2792,48 @@ public class PhoneWindowManager implements WindowManagerPolicy { // It is possible that we may navigate away from this task before the double // press is detected, as a result of the first press, so we save the current // most recent task before that happens. // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp = mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); if (mShouldEarlyShortPressOnStemPrimary) { mFocusedTaskInfoOnStemPrimarySingleKeyUp = null; if (shouldHandleStemPrimaryEarlyShortPress()) { // Key-up gesture should be triggered only if app doesn't handle it. mDeferredKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1)); KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> { // Save the info of the focused task on screen. This may be used // later to bring the current focused task back to top. For // example, stem primary triple press enables the A11y interface // on top of the current focused task. When early single press is // enabled for stem primary, the focused task could change to // something else upon first key up event. In that case, we will // bring the task recorded by this variable back to top. Then, start // A11y interface. try { mFocusedTaskInfoOnStemPrimarySingleKeyUp = mActivityManagerService.getFocusedRootTaskInfo(); } catch (RemoteException e) { Slog.e( TAG, "StemPrimaryKeyRule: onKeyUp: error while getting " + "focused task " + "info.", e); } stemPrimaryPress(1); }); } } } // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button. private boolean shouldHandleStemPrimaryEarlyShortPress() { return mShouldEarlyShortPressOnStemPrimary && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; } } Loading services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +20 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,26 @@ public final class DeferredKeyActionExecutorTests { assertFalse(action.executed); } @Test public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() { TestAction action1 = new TestAction(); TestAction action2 = new TestAction(); TestAction action3 = new TestAction(); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2); mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3); mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); assertFalse(action1.executed); assertFalse(action2.executed); assertTrue(action3.executed); } static class TestAction implements Runnable { public boolean executed; Loading services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +56 −5 Original line number Diff line number Diff line Loading @@ -19,14 +19,18 @@ package com.android.server.policy; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.os.RemoteException; import android.provider.Settings; Loading @@ -50,6 +54,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_duringSetup_doNothing() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(false); Loading @@ -65,6 +70,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_AfterSetup_openAllApp() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -83,6 +89,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -104,6 +111,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true); setDispatchedKeyHandler(keyEvent -> true); sendKey(KEYCODE_STEM_PRIMARY); Loading Loading @@ -131,6 +139,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -144,6 +153,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideSearchManager(null); mPhoneWindowManager.overrideStatusBarManagerInternal(); Loading @@ -156,7 +166,8 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { @Test public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); Loading @@ -171,14 +182,47 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException { public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior( STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); RootTaskInfo allAppsTask = new RootTaskInfo(); int referenceId = 777; allAppsTask.taskId = referenceId; doReturn(allAppsTask) .when(mPhoneWindowManager.mActivityManagerService) .getFocusedRootTaskInfo(); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertOpenAllAppView(); mPhoneWindowManager.assertSwitchToTask(referenceId); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); } @Test public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior( STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); Loading @@ -187,11 +231,18 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { doReturn(recentTaskInfo).when( mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground(); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test Loading @@ -215,7 +266,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } private void overrideBehavior(String key, int expectedBehavior) { Loading services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +22 −6 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ import android.media.AudioManagerInternal; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.RemoteException; Loading Loading @@ -176,8 +177,9 @@ class TestPhoneWindowManager { private Handler mHandler; private boolean mIsTalkBackEnabled; private boolean mIsTalkBackShortcutGestureEnabled; class TestTalkbackShortcutController extends TalkbackShortcutController { private class TestTalkbackShortcutController extends TalkbackShortcutController { TestTalkbackShortcutController(Context context) { super(context); } Loading @@ -190,13 +192,18 @@ class TestPhoneWindowManager { @Override boolean isTalkBackShortcutGestureEnabled() { return true; return mIsTalkBackShortcutGestureEnabled; } } private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { super(context, funcs, mTestLooper.getLooper()); super(context, funcs); } @Override Looper getLooper() { return mTestLooper.getLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( Loading Loading @@ -410,6 +417,10 @@ class TestPhoneWindowManager { mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress; } void overrideTalkbackShortcutGestureEnabled(boolean enabled) { mIsTalkBackShortcutGestureEnabled = enabled; } // Override assist perform function. void overrideLongPressOnPower(int behavior) { mPhoneWindowManager.mLongPressOnPowerBehavior = behavior; Loading Loading @@ -714,7 +725,7 @@ class TestPhoneWindowManager { } void assertOpenAllAppView() { mTestLooper.dispatchAll(); moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); Loading @@ -728,7 +739,7 @@ class TestPhoneWindowManager { } void assertActivityTargetLaunched(ComponentName targetActivity) { mTestLooper.dispatchAll(); moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); Loading @@ -743,10 +754,15 @@ class TestPhoneWindowManager { expectedModifierState, deviceBus), description(errorMsg)); } void assertSwitchToRecent(int persistentId) throws RemoteException { void assertSwitchToTask(int persistentId) throws RemoteException { mTestLooper.dispatchAll(); verify(mActivityManagerService, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId), isNull()); } void assertTalkBack(boolean expectEnabled) { mTestLooper.dispatchAll(); Assert.assertEquals(expectEnabled, mIsTalkBackEnabled); } } Loading
services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java +16 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,18 @@ class DeferredKeyActionExecutor { getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable(); } /** * Clears all the queued action for given key code. * * @param keyCode the key code whose queued actions will be cleared. */ public void cancelQueuedAction(int keyCode) { TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode); if (actionsBuffer != null) { actionsBuffer.clear(); } } private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) { TimedActionsBuffer buffer = mBuffers.get(keyCode); if (buffer == null || buffer.getDownTime() != downTime) { Loading Loading @@ -146,6 +158,10 @@ class DeferredKeyActionExecutor { mActions.clear(); } void clear() { mActions.clear(); } void dump(String prefix, PrintWriter pw) { if (mExecutable) { pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable"); Loading
services/core/java/com/android/server/policy/PhoneWindowManager.java +71 −8 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUiModeManager; Loading Loading @@ -584,6 +585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mLongPressOnStemPrimaryBehavior; private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp; // The focused task at the time when the first STEM_PRIMARY key was released. This can only // be accessed from the looper thread. private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp; private boolean mHandleVolumeKeysInWM; private boolean mPendingKeyguardOccluded; Loading Loading @@ -2135,12 +2140,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static class Injector { private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; private final Looper mLooper; Injector(Context context, WindowManagerFuncs funcs, Looper looper) { Injector(Context context, WindowManagerFuncs funcs) { mContext = context; mWindowManagerFuncs = funcs; mLooper = looper; } Context getContext() { Loading @@ -2152,7 +2155,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } Looper getLooper() { return mLooper; return Looper.myLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( Loading Loading @@ -2195,7 +2198,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void init(Context context, WindowManagerFuncs funcs) { init(new Injector(context, funcs, Looper.myLooper())); init(new Injector(context, funcs)); } @VisibleForTesting Loading Loading @@ -2723,7 +2726,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onPress(long downTime, int unusedDisplayId) { if (mShouldEarlyShortPressOnStemPrimary) { if (shouldHandleStemPrimaryEarlyShortPress()) { return; } // Short-press should be triggered only if app doesn't handle it. Loading @@ -2747,6 +2750,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (count == 3 && mTriplePressOnStemPrimaryBehavior == TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) { // Cancel any queued actions for current key code to prevent them from being // launched after a11y layer enabled. If the action happens early, // undoEarlySinglePress will make sure the correct task is on top. mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); undoEarlySinglePress(); stemPrimaryPress(count); } else { // Other multi-press gestures should be triggered only if app doesn't handle it. Loading @@ -2755,6 +2763,27 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } /** * This method undo the previously launched early-single-press action by bringing the * focused task before launching early-single-press back to top. */ private void undoEarlySinglePress() { if (shouldHandleStemPrimaryEarlyShortPress() && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) { try { mActivityManagerService.startActivityFromRecents( mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null); } catch (RemoteException | IllegalArgumentException e) { Slog.e( TAG, "Failed to start task " + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId + " from recents", e); } } } @Override void onKeyUp(long eventTime, int count, int unusedDisplayId) { if (count == 1) { Loading @@ -2763,14 +2792,48 @@ public class PhoneWindowManager implements WindowManagerPolicy { // It is possible that we may navigate away from this task before the double // press is detected, as a result of the first press, so we save the current // most recent task before that happens. // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp = mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); if (mShouldEarlyShortPressOnStemPrimary) { mFocusedTaskInfoOnStemPrimarySingleKeyUp = null; if (shouldHandleStemPrimaryEarlyShortPress()) { // Key-up gesture should be triggered only if app doesn't handle it. mDeferredKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1)); KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> { // Save the info of the focused task on screen. This may be used // later to bring the current focused task back to top. For // example, stem primary triple press enables the A11y interface // on top of the current focused task. When early single press is // enabled for stem primary, the focused task could change to // something else upon first key up event. In that case, we will // bring the task recorded by this variable back to top. Then, start // A11y interface. try { mFocusedTaskInfoOnStemPrimarySingleKeyUp = mActivityManagerService.getFocusedRootTaskInfo(); } catch (RemoteException e) { Slog.e( TAG, "StemPrimaryKeyRule: onKeyUp: error while getting " + "focused task " + "info.", e); } stemPrimaryPress(1); }); } } } // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button. private boolean shouldHandleStemPrimaryEarlyShortPress() { return mShouldEarlyShortPressOnStemPrimary && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; } } Loading
services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +20 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,26 @@ public final class DeferredKeyActionExecutorTests { assertFalse(action.executed); } @Test public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() { TestAction action1 = new TestAction(); TestAction action2 = new TestAction(); TestAction action3 = new TestAction(); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2); mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY); mKeyActionExecutor.queueKeyAction( KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3); mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); assertFalse(action1.executed); assertFalse(action2.executed); assertTrue(action3.executed); } static class TestAction implements Runnable { public boolean executed; Loading
services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +56 −5 Original line number Diff line number Diff line Loading @@ -19,14 +19,18 @@ package com.android.server.policy; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.os.RemoteException; import android.provider.Settings; Loading @@ -50,6 +54,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_duringSetup_doNothing() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(false); Loading @@ -65,6 +70,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { public void stemSingleKey_AfterSetup_openAllApp() { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -83,6 +89,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideStartActivity(); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -104,6 +111,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true); setDispatchedKeyHandler(keyEvent -> true); sendKey(KEYCODE_STEM_PRIMARY); Loading Loading @@ -131,6 +139,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideIsUserSetupComplete(true); Loading @@ -144,6 +153,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { STEM_PRIMARY_BUTTON_LONG_PRESS, LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideSearchManager(null); mPhoneWindowManager.overrideStatusBarManagerInternal(); Loading @@ -156,7 +166,8 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { @Test public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); Loading @@ -171,14 +182,47 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException { public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior( STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); RootTaskInfo allAppsTask = new RootTaskInfo(); int referenceId = 777; allAppsTask.taskId = referenceId; doReturn(allAppsTask) .when(mPhoneWindowManager.mActivityManagerService) .getFocusedRootTaskInfo(); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertOpenAllAppView(); mPhoneWindowManager.assertSwitchToTask(referenceId); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); } @Test public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException { overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); overrideBehavior( STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY); setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true); mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); mPhoneWindowManager.overrideIsUserSetupComplete(true); RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); Loading @@ -187,11 +231,18 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { doReturn(recentTaskInfo).when( mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground(); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true); sendKey(KEYCODE_STEM_PRIMARY); sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } @Test Loading @@ -215,7 +266,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { sendKey(KEYCODE_STEM_PRIMARY); mPhoneWindowManager.assertNotOpenAllAppView(); mPhoneWindowManager.assertSwitchToRecent(referenceId); mPhoneWindowManager.assertSwitchToTask(referenceId); } private void overrideBehavior(String key, int expectedBehavior) { Loading
services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +22 −6 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ import android.media.AudioManagerInternal; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.RemoteException; Loading Loading @@ -176,8 +177,9 @@ class TestPhoneWindowManager { private Handler mHandler; private boolean mIsTalkBackEnabled; private boolean mIsTalkBackShortcutGestureEnabled; class TestTalkbackShortcutController extends TalkbackShortcutController { private class TestTalkbackShortcutController extends TalkbackShortcutController { TestTalkbackShortcutController(Context context) { super(context); } Loading @@ -190,13 +192,18 @@ class TestPhoneWindowManager { @Override boolean isTalkBackShortcutGestureEnabled() { return true; return mIsTalkBackShortcutGestureEnabled; } } private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { super(context, funcs, mTestLooper.getLooper()); super(context, funcs); } @Override Looper getLooper() { return mTestLooper.getLooper(); } AccessibilityShortcutController getAccessibilityShortcutController( Loading Loading @@ -410,6 +417,10 @@ class TestPhoneWindowManager { mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress; } void overrideTalkbackShortcutGestureEnabled(boolean enabled) { mIsTalkBackShortcutGestureEnabled = enabled; } // Override assist perform function. void overrideLongPressOnPower(int behavior) { mPhoneWindowManager.mLongPressOnPowerBehavior = behavior; Loading Loading @@ -714,7 +725,7 @@ class TestPhoneWindowManager { } void assertOpenAllAppView() { mTestLooper.dispatchAll(); moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); Loading @@ -728,7 +739,7 @@ class TestPhoneWindowManager { } void assertActivityTargetLaunched(ComponentName targetActivity) { mTestLooper.dispatchAll(); moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); Loading @@ -743,10 +754,15 @@ class TestPhoneWindowManager { expectedModifierState, deviceBus), description(errorMsg)); } void assertSwitchToRecent(int persistentId) throws RemoteException { void assertSwitchToTask(int persistentId) throws RemoteException { mTestLooper.dispatchAll(); verify(mActivityManagerService, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId), isNull()); } void assertTalkBack(boolean expectEnabled) { mTestLooper.dispatchAll(); Assert.assertEquals(expectEnabled, mIsTalkBackEnabled); } }