Merge "Immediately tear down VCN network when entering Airplane Mode" into sc-v2-dev
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 046232a..2dac81c 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -129,7 +129,11 @@
      * The index of the element in the tree in prefix order. This should be used for z-layering
      * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
      * happen.
+     * @deprecated WindowManager may set a z-order different from the prefix order, and has set the
+     *             correct layer for the animation leash already, so this should not be used for
+     *             layer any more.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public final int prefixOrderIndex;
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 7020603..fb80f1c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -436,7 +436,6 @@
                 .withAlpha(1f)
                 .withMatrix(matrix)
                 .withWindowCrop(windowCrop)
-                .withLayer(window.prefixOrderIndex)
                 .withCornerRadius(cornerRadius)
                 .withVisibility(true)
                 .build()
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index f1deb30..60cc57d 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,13 +35,14 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.media.AudioAttributes;
 import android.os.Process;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.MathUtils;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -83,6 +84,7 @@
  */
 @StatusBarComponent.StatusBarScope
 public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
+    private static final String TAG = "LockIconViewController";
     private static final float sDefaultDensity =
             (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
     private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
@@ -91,6 +93,7 @@
                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                 .build();
+    private static final long LONG_PRESS_TIMEOUT = 150L; // milliseconds
 
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardViewController mKeyguardViewController;
@@ -112,6 +115,12 @@
     @Nullable private final Vibrator mVibrator;
     @Nullable private final AuthRippleController mAuthRippleController;
 
+    // Tracks the velocity of a touch to help filter out the touches that move too fast.
+    private VelocityTracker mVelocityTracker;
+    // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
+    private int mActivePointerId = -1;
+    private VibrationEffect mTick;
+
     private boolean mIsDozing;
     private boolean mIsBouncerShowing;
     private boolean mRunningFPS;
@@ -122,6 +131,7 @@
     private boolean mUserUnlockedWithBiometric;
     private Runnable mCancelDelayedUpdateVisibilityRunnable;
     private Runnable mOnGestureDetectedRunnable;
+    private Runnable mLongPressCancelRunnable;
 
     private boolean mUdfpsSupported;
     private float mHeightPixels;
@@ -181,7 +191,7 @@
         mView.setImageDrawable(mIcon);
         mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
         mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
-        dumpManager.registerDumpable("LockIconViewController", this);
+        dumpManager.registerDumpable(TAG, this);
     }
 
     @Override
@@ -320,7 +330,7 @@
                         getResources().getString(R.string.accessibility_enter_hint));
         public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(v, info);
-            if (isClickable()) {
+            if (isActionable()) {
                 if (mShowLockIcon) {
                     info.addAction(mAccessibilityAuthenticateHint);
                 } else if (mShowUnlockIcon) {
@@ -475,7 +485,7 @@
                 @Override
                 public void onKeyguardVisibilityChanged(boolean showing) {
                     // reset mIsBouncerShowing state in case it was preemptively set
-                    // onAffordanceClick
+                    // onLongPress
                     mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
                     updateVisibility();
                 }
@@ -569,104 +579,79 @@
         }
     };
 
-    private final GestureDetector mGestureDetector =
-            new GestureDetector(new SimpleOnGestureListener() {
-                public boolean onDown(MotionEvent e) {
-                    if (!isClickable()) {
-                        mDownDetected = false;
-                        return false;
-                    }
-
-                    // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
-                    // MotionEvent.ACTION_UP (see #onTouchEvent)
-                    if (mVibrator != null && !mDownDetected) {
-                        mVibrator.vibrate(
-                                Process.myUid(),
-                                getContext().getOpPackageName(),
-                                UdfpsController.EFFECT_CLICK,
-                                "lockIcon-onDown",
-                                VIBRATION_SONIFICATION_ATTRIBUTES);
-                    }
-
-                    mDownDetected = true;
-                    return true;
-                }
-
-                public void onLongPress(MotionEvent e) {
-                    if (!wasClickableOnDownEvent()) {
-                        return;
-                    }
-
-                    if (onAffordanceClick() && mVibrator != null) {
-                        // only vibrate if the click went through and wasn't intercepted by falsing
-                        mVibrator.vibrate(
-                                Process.myUid(),
-                                getContext().getOpPackageName(),
-                                UdfpsController.EFFECT_CLICK,
-                                "lockIcon-onLongPress",
-                                VIBRATION_SONIFICATION_ATTRIBUTES);
-                    }
-                }
-
-                public boolean onSingleTapUp(MotionEvent e) {
-                    if (!wasClickableOnDownEvent()) {
-                        return false;
-                    }
-                    onAffordanceClick();
-                    return true;
-                }
-
-                public boolean onFling(MotionEvent e1, MotionEvent e2,
-                        float velocityX, float velocityY) {
-                    if (!wasClickableOnDownEvent()) {
-                        return false;
-                    }
-                    onAffordanceClick();
-                    return true;
-                }
-
-                private boolean wasClickableOnDownEvent() {
-                    return mDownDetected;
-                }
-
-                /**
-                 * Whether we tried to launch the affordance.
-                 *
-                 * If falsing intercepts the click, returns false.
-                 */
-                private boolean onAffordanceClick() {
-                    if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
-                        return false;
-                    }
-
-                    // pre-emptively set to true to hide view
-                    mIsBouncerShowing = true;
-                    if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
-                        mAuthRippleController.showRipple(FINGERPRINT);
-                    }
-                    updateVisibility();
-                    if (mOnGestureDetectedRunnable != null) {
-                        mOnGestureDetectedRunnable.run();
-                    }
-                    mKeyguardViewController.showBouncer(/* scrim */ true);
-                    return true;
-                }
-            });
-
     /**
-     * Send touch events to this view and handles it if the touch is within this view and we are
-     * in a 'clickable' state
-     * @return whether to intercept the touch event
+     * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true.
+     * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon
+     * area for {@link #LONG_PRESS_TIMEOUT} ms.
+     *
+     * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}.
      */
     public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
-        if (onInterceptTouchEvent(event)) {
-            mOnGestureDetectedRunnable = onGestureDetectedRunnable;
-            mGestureDetector.onTouchEvent(event);
-            return true;
+        if (!onInterceptTouchEvent(event)) {
+            cancelTouches();
+            return false;
         }
 
-        mDownDetected = false;
-        return false;
+        mOnGestureDetectedRunnable = onGestureDetectedRunnable;
+        switch(event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_HOVER_ENTER:
+                if (mVibrator != null && !mDownDetected) {
+                    if (mTick == null) {
+                        mTick = UdfpsController.lowTick(getContext(), true,
+                                LONG_PRESS_TIMEOUT);
+                    }
+                    mVibrator.vibrate(
+                            Process.myUid(),
+                            getContext().getOpPackageName(),
+                            mTick,
+                            "lock-icon-tick",
+                            VIBRATION_SONIFICATION_ATTRIBUTES);
+                }
+
+                // The pointer that causes ACTION_DOWN is always at index 0.
+                // We need to persist its ID to track it during ACTION_MOVE that could include
+                // data for many other pointers because of multi-touch support.
+                mActivePointerId = event.getPointerId(0);
+                if (mVelocityTracker == null) {
+                    // To simplify the lifecycle of the velocity tracker, make sure it's never null
+                    // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
+                    mVelocityTracker = VelocityTracker.obtain();
+                } else {
+                    // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+                    // ACTION_DOWN, in that case we should just reuse the old instance.
+                    mVelocityTracker.clear();
+                }
+                mVelocityTracker.addMovement(event);
+
+                mDownDetected = true;
+                mLongPressCancelRunnable = mExecutor.executeDelayed(
+                        this::onLongPress, LONG_PRESS_TIMEOUT);
+                break;
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_HOVER_MOVE:
+                mVelocityTracker.addMovement(event);
+                // Compute pointer velocity in pixels per second.
+                mVelocityTracker.computeCurrentVelocity(1000);
+                float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
+                        mActivePointerId);
+                if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
+                        && UdfpsController.exceedsVelocityThreshold(velocity)) {
+                    Log.v(TAG, "lock icon long-press rescheduled due to "
+                            + "high pointer velocity=" + velocity);
+                    mLongPressCancelRunnable.run();
+                    mLongPressCancelRunnable = mExecutor.executeDelayed(
+                            this::onLongPress, LONG_PRESS_TIMEOUT);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                cancelTouches();
+                break;
+        }
+
+        return true;
     }
 
     /**
@@ -674,7 +659,7 @@
      * bounds.
      */
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (!inLockIconArea(event) || !isClickable()) {
+        if (!inLockIconArea(event) || !isActionable()) {
             return false;
         }
 
@@ -685,13 +670,48 @@
         return mDownDetected;
     }
 
+    private void onLongPress() {
+        cancelTouches();
+        if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+            Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
+            return;
+        }
+
+        // pre-emptively set to true to hide view
+        mIsBouncerShowing = true;
+        if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+            mAuthRippleController.showRipple(FINGERPRINT);
+        }
+        updateVisibility();
+        if (mOnGestureDetectedRunnable != null) {
+            mOnGestureDetectedRunnable.run();
+        }
+        mKeyguardViewController.showBouncer(/* scrim */ true);
+    }
+
+
+    private void cancelTouches() {
+        mDownDetected = false;
+        if (mLongPressCancelRunnable != null) {
+            mLongPressCancelRunnable.run();
+        }
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+        if (mVibrator != null) {
+            mVibrator.cancel();
+        }
+    }
+
+
     private boolean inLockIconArea(MotionEvent event) {
         return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
                 && (mView.getVisibility() == View.VISIBLE
                 || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE));
     }
 
-    private boolean isClickable() {
+    private boolean isActionable() {
         return mUdfpsSupported || mShowUnlockIcon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 09f2af4..9808045 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -103,6 +103,7 @@
 public class UdfpsController implements DozeReceiver {
     private static final String TAG = "UdfpsController";
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
+    private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds
 
     // Minimum required delay between consecutive touch logs in milliseconds.
     private static final long MIN_TOUCH_LOG_INTERVAL = 50;
@@ -164,8 +165,7 @@
     private boolean mAttemptedToDismissKeyguard;
     private Set<Callback> mCallbacks = new HashSet<>();
 
-    // by default, use low tick
-    private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
+    private static final int DEFAULT_TICK = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
     private final VibrationEffect mTick;
 
     @VisibleForTesting
@@ -327,12 +327,23 @@
         }
     }
 
-    private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
+    /**
+     * Calculate the pointer speed given a velocity tracker and the pointer id.
+     * This assumes that the velocity tracker has already been passed all relevant motion events.
+     */
+    public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
         final float vx = tracker.getXVelocity(pointerId);
         final float vy = tracker.getYVelocity(pointerId);
         return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
     }
 
+    /**
+     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
+     */
+    public static boolean exceedsVelocityThreshold(float velocity) {
+        return velocity > 750f;
+    }
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -467,7 +478,7 @@
                         final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
                         final float minor = event.getTouchMinor(idx);
                         final float major = event.getTouchMajor(idx);
-                        final boolean exceedsVelocityThreshold = v > 750f;
+                        final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v);
                         final String touchInfo = String.format(
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
@@ -575,7 +586,7 @@
         mConfigurationController = configurationController;
         mSystemClock = systemClock;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mTick = lowTick();
+        mTick = lowTick(context, false /* useShortRampup */, DEFAULT_VIBRATION_DURATION);
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -610,32 +621,43 @@
         udfpsHapticsSimulator.setUdfpsController(this);
     }
 
-    private VibrationEffect lowTick() {
-        boolean useLowTickDefault = mContext.getResources()
+    /**
+     * Returns the continuous low tick effect that starts playing on the udfps finger-down event.
+     */
+    public static VibrationEffect lowTick(
+            Context context,
+            boolean useShortRampUp,
+            long duration
+    ) {
+        boolean useLowTickDefault = context.getResources()
                 .getBoolean(R.bool.config_udfpsUseLowTick);
+        int primitiveTick = DEFAULT_TICK;
         if (Settings.Global.getFloat(
-                mContext.getContentResolver(),
+                context.getContentResolver(),
                 "tick-low", useLowTickDefault ? 1 : 0) == 0) {
-            mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK;
+            primitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK;
         }
         float tickIntensity = Settings.Global.getFloat(
-                mContext.getContentResolver(),
+                context.getContentResolver(),
                 "tick-intensity",
-                mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity));
+                context.getResources().getFloat(R.dimen.config_udfpsTickIntensity));
         int tickDelay = Settings.Global.getInt(
-                mContext.getContentResolver(),
+                context.getContentResolver(),
                 "tick-delay",
-                mContext.getResources().getInteger(R.integer.config_udfpsTickDelay));
+                context.getResources().getInteger(R.integer.config_udfpsTickDelay));
 
         VibrationEffect.Composition composition = VibrationEffect.startComposition();
-        composition.addPrimitive(mPrimitiveTick, tickIntensity, 0);
-        int primitives = 1000 / tickDelay;
+        composition.addPrimitive(primitiveTick, tickIntensity, 0);
+        int primitives = (int) (duration / tickDelay);
         float[] rampUp = new float[]{.48f, .58f, .69f, .83f};
+        if (useShortRampUp) {
+            rampUp = new float[]{.5f, .7f};
+        }
         for (int i = 0; i < rampUp.length; i++) {
-            composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay);
+            composition.addPrimitive(primitiveTick, tickIntensity * rampUp[i], tickDelay);
         }
         for (int i = rampUp.length; i < primitives; i++) {
-            composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay);
+            composition.addPrimitive(primitiveTick, tickIntensity, tickDelay);
         }
         return composition.compose();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
index 71edbc0..d17eadd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
@@ -19,6 +19,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
 
 import android.provider.DeviceConfig;
@@ -71,7 +72,9 @@
             return Result.passed(0);
         }
 
-        if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) {
+        if (interactionType == LEFT_AFFORDANCE
+                || interactionType == RIGHT_AFFORDANCE
+                || interactionType == LOCK_ICON) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ab08865..5477c19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -119,6 +119,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -726,38 +727,59 @@
         }
 
         if (DEBUG) {
-            int y = mTopPadding;
-            mDebugPaint.setColor(Color.RED);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = getLayoutHeight();
-            mDebugPaint.setColor(Color.YELLOW);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) mMaxLayoutHeight;
-            mDebugPaint.setColor(Color.MAGENTA);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            if (mKeyguardBottomPadding >= 0) {
-                y = getHeight() - (int) mKeyguardBottomPadding;
-                mDebugPaint.setColor(Color.GRAY);
-                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-            }
-
-            y = getHeight() - getEmptyBottomMargin();
-            mDebugPaint.setColor(Color.GREEN);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) (mAmbientState.getStackY());
-            mDebugPaint.setColor(Color.CYAN);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
-            mDebugPaint.setColor(Color.BLUE);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            onDrawDebug(canvas);
         }
     }
 
+    /** Used to track the Y positions that were already used to draw debug text labels. */
+    private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS =
+            DEBUG ? new HashSet<>() : Collections.emptySet();
+
+    private void onDrawDebug(Canvas canvas) {
+        DEBUG_TEXT_USED_Y_POSITIONS.clear();
+
+        int y = mTopPadding;
+        drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding");
+
+        y = getLayoutHeight();
+        drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight()");
+
+        y = (int) mMaxLayoutHeight;
+        drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight");
+
+        if (mKeyguardBottomPadding >= 0) {
+            y = getHeight() - (int) mKeyguardBottomPadding;
+            drawDebugInfo(canvas, y, Color.GRAY,
+                    /* label= */ "getHeight() - mKeyguardBottomPadding");
+        }
+
+        y = getHeight() - getEmptyBottomMargin();
+        drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeight() - getEmptyBottomMargin()");
+
+        y = (int) (mAmbientState.getStackY());
+        drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY()");
+
+        y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+        drawDebugInfo(canvas, y, Color.BLUE,
+                /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight()");
+    }
+
+    private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+        mDebugPaint.setColor(color);
+        canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ getWidth(), /* stopY= */ y,
+                mDebugPaint);
+        canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+    }
+
+    private int computeDebugYTextPosition(int lineY) {
+        int textY = lineY;
+        while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) {
+            textY = (int) (textY + mDebugPaint.getTextSize());
+        }
+        DEBUG_TEXT_USED_Y_POSITIONS.add(textY);
+        return textY;
+    }
+
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
     private void drawBackground(Canvas canvas) {
         int lockScreenLeft = mSidePaddings;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ba11e9c..7a70bfe 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24299,24 +24299,24 @@
         }
         enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
                 true /* checkShell */, "stop package");
-        boolean shouldUnhibernate = false;
         // writer
         synchronized (mLock) {
             final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (ps != null && ps.getStopped(userId) && !stopped) {
-                shouldUnhibernate = true;
-            }
             if (!shouldFilterApplicationLocked(ps, callingUid, userId)
                     && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) {
                 scheduleWritePackageRestrictionsLocked(userId);
             }
         }
-        if (shouldUnhibernate) {
+        // If this would cause the app to leave force-stop, then also make sure to unhibernate the
+        // app if needed.
+        if (!stopped) {
             mHandler.post(() -> {
                 AppHibernationManagerInternal ah =
                         mInjector.getLocalService(AppHibernationManagerInternal.class);
-                ah.setHibernatingForUser(packageName, userId, false);
-                ah.setHibernatingGlobally(packageName, false);
+                if (ah != null && ah.isHibernatingForUser(packageName, userId)) {
+                    ah.setHibernatingForUser(packageName, userId, false);
+                    ah.setHibernatingGlobally(packageName, false);
+                }
             });
         }
     }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 2be29d4..dfff76d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -50,6 +50,10 @@
 import static android.util.MathUtils.constrain;
 
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
@@ -4387,8 +4391,9 @@
                 final int userId = userInfo.getUserHandle().getIdentifier();
 
                 if (isAccessibilityShortcutUser(mContext, userId)) {
-                    final int software_shortcut_type = Settings.Secure.getIntForUser(resolver,
-                            Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId);
+                    final int software_shortcut_type = convertToAccessibilityShortcutType(
+                            Settings.Secure.getIntForUser(resolver,
+                                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId));
                     final String software_shortcut_list = Settings.Secure.getStringForUser(resolver,
                             Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
                     final int software_shortcut_service_num = countAccessibilityServices(
@@ -4509,6 +4514,19 @@
                 && !TextUtils.isEmpty(software_string);
     }
 
+    private int convertToAccessibilityShortcutType(int shortcutType) {
+        switch (shortcutType) {
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+            default:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+        }
+    }
+
     // Thermal event received from vendor thermal management subsystem
     private static final class ThermalEventListener extends IThermalEventListener.Stub {
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index fd97557..e053dc3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -29,6 +29,7 @@
 import com.android.server.apphibernation.AppHibernationService
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.testutils.whenever
+import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -100,8 +101,11 @@
             rule.system().dataAppDirectory)
         val pm = createPackageManagerService()
         rule.system().validateFinalState()
-        val ps = pm.getPackageSetting(TEST_PACKAGE_NAME)
-        ps!!.setStopped(true, TEST_USER_ID)
+
+        TestableLooper.get(this).processAllMessages()
+
+        whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+            .thenReturn(true)
 
         pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
 
@@ -112,6 +116,31 @@
     }
 
     @Test
+    fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() {
+        whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
+            .thenReturn(null)
+
+        rule.system().stageScanExistingPackage(
+            TEST_PACKAGE_NAME,
+            1L,
+            rule.system().dataAppDirectory)
+        val pm = createPackageManagerService()
+        rule.system().validateFinalState()
+
+        TestableLooper.get(this).processAllMessages()
+
+        whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+            .thenReturn(true)
+
+        try {
+            pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
+            TestableLooper.get(this).processAllMessages()
+        } catch (e: Exception) {
+            Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e")
+        }
+    }
+
+    @Test
     fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
         rule.system().stageScanExistingPackage(
             TEST_PACKAGE_NAME,