Merge "Nullability Annotations replacement" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f794fef..893a87f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -48,12 +48,14 @@
     private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
     private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
     private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100;
+    private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
+    private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+    private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 150;
     /**
      * Additional scale applied to expanded view when it is positioned inside a magnetic target.
      */
-    private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f;
-    private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
-    private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+    private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.6f;
+    private static final float EXPANDED_VIEW_DRAG_SCALE = 0.5f;
 
     /** Spring config for the expanded view scale-in animation. */
     private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -72,6 +74,7 @@
     private final Context mContext;
     private final BubbleBarLayerView mLayerView;
     private final BubblePositioner mPositioner;
+    private final int[] mTmpLocation = new int[2];
 
     private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded = false;
@@ -220,6 +223,25 @@
     }
 
     /**
+     * Animate the expanded bubble when it is being dragged
+     */
+    public void animateStartDrag() {
+        final BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
+            Log.w(TAG, "Trying to animate start drag without a bubble");
+            return;
+        }
+        bbev.setPivotX(bbev.getWidth() / 2f);
+        bbev.setPivotY(0f);
+        bbev.animate()
+                .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+                .scaleY(EXPANDED_VIEW_DRAG_SCALE)
+                .setInterpolator(Interpolators.EMPHASIZED)
+                .setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION)
+                .start();
+    }
+
+    /**
      * Animates dismissal of currently expanded bubble
      *
      * @param endRunnable a runnable to run at the end of the animation
@@ -261,7 +283,10 @@
                 .setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
                 .withStartAction(() -> bbev.setAnimating(true))
-                .withEndAction(() -> bbev.setAnimating(false))
+                .withEndAction(() -> {
+                    bbev.setAnimating(false);
+                    bbev.resetPivot();
+                })
                 .start();
     }
 
@@ -277,25 +302,52 @@
             Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
             return;
         }
-        Point expandedViewCenter = getViewCenterOnScreen(bbev);
-
-        // Calculate the difference between the target's center coordinates and the object's.
-        // Animating the object's x/y properties by these values will center the object on top
-        // of the magnetic target.
-        float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x;
-        float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y;
 
         // Calculate scale of expanded view so it fits inside the magnetic target
         float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight());
-        float targetMaxSide = Math.max(target.getTargetView().getWidth(),
-                target.getTargetView().getHeight());
-        float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide;
+        View targetView = target.getTargetView();
+        float targetMaxSide = Math.max(targetView.getWidth(), targetView.getHeight());
+        // Reduce target size to have some padding between the target and expanded view
+        targetMaxSide *= EXPANDED_VIEW_IN_TARGET_SCALE;
+        float scaleInTarget = targetMaxSide / bbevMaxSide;
+
+        // Scale around the top center of the expanded view. Same as when dragging.
+        bbev.setPivotX(bbev.getWidth() / 2f);
+        bbev.setPivotY(0);
+
+        // When the view animates into the target, it is scaled down with the pivot at center top.
+        // Find the point on the view that would be the center of the view at its final scale.
+        // Once we know that, we can calculate x and y distance from the center of the target view
+        // and use that for the translation animation to ensure that the view at final scale is
+        // placed at the center of the target.
+
+        // Set mTmpLocation to the current location of the view on the screen, taking into account
+        // any scale applied.
+        bbev.getLocationOnScreen(mTmpLocation);
+        // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+        // x-axis will be the same as the center of the view at current size.
+        // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+        // center of the view at its current size.
+        float currentWidth = bbev.getWidth() * bbev.getScaleX();
+        mTmpLocation[0] += currentWidth / 2;
+        // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+        // remains the same.
+        // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+        // moved down by half of the height at final scale.
+        float targetHeight = bbev.getHeight() * scaleInTarget;
+        mTmpLocation[1] += targetHeight / 2;
+        // mTmpLocation is now set to the point on the view that will be the center of the view once
+        // scale is applied.
+
+        // Calculate the difference between the target's center coordinates and mTmpLocation
+        float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+        float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
 
         bbev.animate()
                 .translationX(bbev.getTranslationX() + xDiff)
                 .translationY(bbev.getTranslationY() + yDiff)
-                .scaleX(scale)
-                .scaleY(scale)
+                .scaleX(scaleInTarget)
+                .scaleY(scaleInTarget)
                 .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED)
                 .withStartAction(() -> bbev.setAnimating(true))
@@ -319,8 +371,8 @@
         }
         expandedView
                 .animate()
-                .scaleX(1f)
-                .scaleY(1f)
+                .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+                .scaleY(EXPANDED_VIEW_DRAG_SCALE)
                 .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED)
                 .withStartAction(() -> expandedView.setAnimating(true))
@@ -385,12 +437,4 @@
         final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         return new Size(width, height);
     }
-
-    private Point getViewCenterOnScreen(View view) {
-        Point center = new Point();
-        int[] onScreenLocation = view.getLocationOnScreen();
-        center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f));
-        center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f));
-        return center;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index d215450..5e634a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -74,6 +74,9 @@
     }
 
     private inner class HandleDragListener : RelativeTouchListener() {
+
+        private var isMoving = false
+
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
             return !expandedView.isAnimating
@@ -87,6 +90,10 @@
             dx: Float,
             dy: Float
         ) {
+            if (!isMoving) {
+                isMoving = true
+                animationHelper.animateStartDrag()
+            }
             expandedView.translationX = expandedViewInitialTranslationX + dx
             expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
@@ -114,6 +121,7 @@
                 animationHelper.animateToRestPosition()
                 dismissView.hide()
             }
+            isMoving = false
         }
     }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index b4c3850..782b850 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -268,3 +268,11 @@
     description: "Adds haptic feedback to the volume slider."
     bug: "316953430"
 }
+
+flag {
+    name: "screenshare_notification_hiding"
+    namespace: "systemui"
+    description: "Enable hiding of notifications during screenshare"
+    bug: "312784809"
+}
+
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 55d8a0f..8e79922a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -41,3 +41,12 @@
   description: "This flag controls the fix for notifications autogroup summary icon updates"
   bug: "227693160"
 }
+
+flag {
+  name: "sensitive_notification_app_protection"
+  namespace: "systemui"
+  description: "This flag controls the sensitive notification app protections while screen sharing"
+  bug: "312784351"
+  # Referenced in WM where WM starts before DeviceConfig
+  is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
index b531b0e..611e4ed 100644
--- a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
+++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
@@ -76,6 +76,18 @@
         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 @@
             mActions.clear();
         }
 
+        void clear() {
+            mActions.clear();
+        }
+
         void dump(String prefix, PrintWriter pw) {
             if (mExecutable) {
                 pw.println(prefix + "  " + KeyEvent.keyCodeToString(mKeyCode) + ": executable");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e8b54d58..3000a1c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -103,6 +103,7 @@
 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 @@
     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 @@
     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 @@
         }
 
         Looper getLooper() {
-            return mLooper;
+            return Looper.myLooper();
         }
 
         AccessibilityShortcutController getAccessibilityShortcutController(
@@ -2195,7 +2198,7 @@
     /** {@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 @@
 
         @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 @@
             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 @@
             }
         }
 
+        /**
+         * 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,15 +2792,49 @@
                 // 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;
+        }
     }
 
     private void initSingleKeyGestureRules(Looper looper) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
index d2ef180..ca3787e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -95,6 +95,26 @@
         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;
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index f7ad2a8..50d37ec 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -19,14 +19,18 @@
 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 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 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 @@
                 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 @@
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
         mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
         setDispatchedKeyHandler(keyEvent -> true);
 
         sendKey(KEYCODE_STEM_PRIMARY);
@@ -131,6 +139,7 @@
                 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 @@
                 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 @@
     @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 @@
         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();
@@ -189,9 +233,16 @@
 
         sendKey(KEYCODE_STEM_PRIMARY);
         sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
 
         mPhoneWindowManager.assertNotOpenAllAppView();
-        mPhoneWindowManager.assertSwitchToRecent(referenceId);
+        mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertNotOpenAllAppView();
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
     @Test
@@ -215,7 +266,7 @@
         sendKey(KEYCODE_STEM_PRIMARY);
 
         mPhoneWindowManager.assertNotOpenAllAppView();
-        mPhoneWindowManager.assertSwitchToRecent(referenceId);
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
     private void overrideBehavior(String key, int expectedBehavior) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 0678210..7c2f7ee 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -73,6 +73,7 @@
 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 @@
     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 @@
 
         @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 @@
         mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress;
     }
 
+    void overrideTalkbackShortcutGestureEnabled(boolean enabled) {
+        mIsTalkBackShortcutGestureEnabled = enabled;
+    }
+
      // Override assist perform function.
     void overrideLongPressOnPower(int behavior) {
         mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -714,7 +725,7 @@
     }
 
     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 @@
     }
 
     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 @@
                         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);
+    }
 }