Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c447ffd8 authored by Chen Bai's avatar Chen Bai
Browse files

clobber wom: move onKeyUp focused task on top for triple stem A11y

- If A11y talk back short cut is enabled, the expeceted behavior is
  seeing green circle on current focused task. With early short press
  for stem(config_shortPressEarlyOnStemPrimary), the all_app is launched
  on first press, so, we need to memorize the focused task before that
  and relaunch the focused task upon triple press to keep the task on
  screen the same.

Test: atest WmTests:StemKeyGestureTests
BUG: 311006348
Change-Id: I4d1030c668e5bd10d14249355568b4ec32e29e22
parent 61d55913
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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) {
@@ -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");
+71 −8
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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() {
@@ -2152,7 +2155,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        }

        Looper getLooper() {
            return mLooper;
            return Looper.myLooper();
        }

        AccessibilityShortcutController getAccessibilityShortcutController(
@@ -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
@@ -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.
@@ -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.
@@ -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) {
@@ -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;
        }
    }

+20 −0
Original line number Diff line number Diff line
@@ -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;

+56 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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);

@@ -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);
@@ -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);
@@ -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);
@@ -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);

@@ -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();
@@ -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);
@@ -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();
@@ -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
@@ -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) {
+22 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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);
        }
@@ -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(
@@ -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;
@@ -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));
@@ -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));
@@ -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);
    }
}