Merge "Remove placeholder namespace for media_solutions flag" into main
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 4851279..d0d76a4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -454,12 +454,11 @@
      */
     protected void onStart() {
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
-        if (mContext != null
+        if (allowsRegisterDefaultOnBackInvokedCallback() && mContext != null
                 && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
             // Add onBackPressed as default back behavior.
             mDefaultBackCallback = this::onBackPressed;
             getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
-            mDefaultBackCallback = null;
         }
     }
 
@@ -470,9 +469,18 @@
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
         if (mDefaultBackCallback != null) {
             getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+            mDefaultBackCallback = null;
         }
     }
 
+    /**
+     * Whether this dialog allows to register the default onBackInvokedCallback.
+     * @hide
+     */
+    protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+        return true;
+    }
+
     private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
     private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
 
@@ -697,7 +705,8 @@
         if (event.isTracking() && !event.isCanceled()) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_BACK:
-                    if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+                    if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)
+                            || !allowsRegisterDefaultOnBackInvokedCallback()) {
                         onBackPressed();
                         return true;
                     }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 892b45e..7d2ef4d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -12819,7 +12819,6 @@
                 } else {
                     mBackgroundColor = rawColor;
                 }
-                mProtectionColor = COLOR_INVALID;  // filled in at the end
                 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
                         ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
                         mBackgroundColor, 4.5);
@@ -12836,7 +12835,6 @@
             } else {
                 int[] attrs = {
                         R.attr.colorSurface,
-                        R.attr.colorBackgroundFloating,
                         R.attr.textColorPrimary,
                         R.attr.textColorSecondary,
                         R.attr.colorAccent,
@@ -12848,15 +12846,14 @@
                 };
                 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
                     mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
-                    mProtectionColor = getColor(ta, 1, COLOR_INVALID);
-                    mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
-                    mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
-                    mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
-                    mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
-                    mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
-                    mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
-                    mErrorColor = getColor(ta, 8, COLOR_INVALID);
-                    mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
+                    mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID);
+                    mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID);
+                    mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
+                    mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
+                    mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
+                    mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+                    mErrorColor = getColor(ta, 7, COLOR_INVALID);
+                    mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
                 }
                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
                         mBackgroundColor, nightMode);
@@ -12889,9 +12886,7 @@
                 }
             }
             // make sure every color has a valid value
-            if (mProtectionColor == COLOR_INVALID) {
-                mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
-            }
+            mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
         }
 
         /** calculates the contrast color for the non-colorized notifications */
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index c3df17d..529363f 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -344,15 +344,22 @@
         // If there is a label for the launcher intent, then use that as it is typically shorter.
         // Otherwise, just use the top-level application name.
         Intent launchIntent = pm.getLaunchIntentForPackage(mContext.getPackageName());
+        if (launchIntent == null) {
+            return getDefaultCallingApplicationLabel();
+        }
         List<ResolveInfo> infos =
                 pm.queryIntentActivities(
                         launchIntent, PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY));
         if (infos.size() > 0) {
             return infos.get(0).loadLabel(pm);
         }
+        return getDefaultCallingApplicationLabel();
+    }
+
+    private CharSequence getDefaultCallingApplicationLabel() {
         return mContext.getApplicationInfo()
                 .loadSafeLabel(
-                        pm,
+                        mContext.getPackageManager(),
                         /* ellipsizeDip= */ 0,
                         TextUtils.SAFE_STRING_FLAG_SINGLE_LINE
                                 | TextUtils.SAFE_STRING_FLAG_TRIM);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 795eb4a..8f653b3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -576,6 +576,12 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
 
+    /**
+     * Enable the logic to allow hiding the IME caption bar ("fake" IME navigation bar).
+     * @hide
+     */
+    public static final boolean ENABLE_HIDE_IME_CAPTION_BAR = true;
+
     LayoutInflater mInflater;
     TypedArray mThemeAttrs;
     @UnsupportedAppUsage
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 78388ef..c01664e 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,6 +16,8 @@
 
 package android.inputmethodservice;
 
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 
 import android.animation.ValueAnimator;
@@ -230,6 +232,16 @@
 
             setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
                     mDrawLegacyNavigationBarBackground));
+
+            if (ENABLE_HIDE_IME_CAPTION_BAR) {
+                mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
+                    if (mNavigationBarFrame != null) {
+                        boolean visible = insets.isVisible(captionBar());
+                        mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+                    }
+                    return view.onApplyWindowInsets(insets);
+                });
+            }
         }
 
         private void uninstallNavigationBarFrameIfNecessary() {
@@ -240,6 +252,9 @@
             if (parent instanceof ViewGroup) {
                 ((ViewGroup) parent).removeView(mNavigationBarFrame);
             }
+            if (ENABLE_HIDE_IME_CAPTION_BAR) {
+                mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
+            }
             mNavigationBarFrame = null;
         }
 
@@ -414,7 +429,9 @@
                         decor.bringChildToFront(mNavigationBarFrame);
                     }
                 }
-                mNavigationBarFrame.setVisibility(View.VISIBLE);
+                if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+                    mNavigationBarFrame.setVisibility(View.VISIBLE);
+                }
             }
         }
 
@@ -435,6 +452,11 @@
                     mShouldShowImeSwitcherWhenImeIsShown;
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
+            if (ENABLE_HIDE_IME_CAPTION_BAR) {
+                mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
+                        .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
+            }
+
             if (imeDrawsImeNavBar) {
                 installNavigationBarFrameIfNecessary();
                 if (mNavigationBarFrame == null) {
@@ -528,6 +550,16 @@
             return drawLegacyNavigationBarBackground;
         }
 
+        /**
+         * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
+         */
+        private int getImeCaptionBarHeight() {
+            return mImeDrawsImeNavBar
+                    ? mService.getResources().getDimensionPixelSize(
+                            com.android.internal.R.dimen.navigation_bar_frame_height)
+                    : 0;
+        }
+
         @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 5704dac..e4a09a6 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -79,6 +79,13 @@
     @WindowState
     private int mWindowState = WindowState.TOKEN_PENDING;
 
+    @Override
+    protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+        // Do not register OnBackInvokedCallback from Dialog#onStart, InputMethodService will
+        // register CompatOnBackInvokedCallback for input method window.
+        return false;
+    }
+
     /**
      * Set {@link IBinder} window token to the window.
      *
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index b24b45d..08b32bf 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -525,14 +525,14 @@
     public abstract long getDuration();
 
     /**
-     * Checks if a given {@link Vibrator} can play this effect as intended.
+     * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended.
      *
-     * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information
-     * about what counts as supported by a vibrator, and what counts as not.
+     * <p>See {@link VibratorInfo#areVibrationFeaturesSupported(VibrationEffect)} for more
+     * information about what counts as supported by a vibrator, and what counts as not.
      *
      * @hide
      */
-    public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+    public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
 
     /**
      * Returns true if this effect could represent a touch haptic feedback.
@@ -813,9 +813,9 @@
 
         /** @hide */
         @Override
-        public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+        public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
             for (VibrationEffectSegment segment : mSegments) {
-                if (!segment.areVibrationFeaturesSupported(vibrator)) {
+                if (!segment.areVibrationFeaturesSupported(vibratorInfo)) {
                     return false;
                 }
             }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 4e852e3..79e0ca8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -216,9 +216,7 @@
      */
     @TestApi
     public boolean hasFrequencyControl() {
-        // We currently can only control frequency of the vibration using the compose PWLE method.
-        return getInfo().hasCapability(
-                IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        return getInfo().hasFrequencyControl();
     }
 
     /**
@@ -240,7 +238,7 @@
      * @hide
      */
     public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
-        return effect.areVibrationFeaturesSupported(this);
+        return getInfo().areVibrationFeaturesSupported(effect);
     }
 
     /**
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 02e6856..0b7d7c3 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -242,6 +242,17 @@
     }
 
     /**
+     * Check whether the vibrator has frequency control.
+     *
+     * @return True if the hardware can control the frequency of the vibrations, otherwise false.
+     */
+    public boolean hasFrequencyControl() {
+        // We currently can only control frequency of the vibration using the compose PWLE method.
+        return hasCapability(
+                IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+    }
+
+    /**
      * Returns a default value to be applied to composed PWLE effects for braking.
      *
      * @return a supported braking value, one of android.hardware.vibrator.Braking.*
@@ -323,6 +334,23 @@
     }
 
     /**
+     * Query whether or not the vibrator supports all components of a given {@link VibrationEffect}
+     * (i.e. the vibrator can play the given effect as intended).
+     *
+     * <p>See {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more
+     * information on how the vibrator support is determined.
+     *
+     * @param effect the {@link VibrationEffect} to check if it is supported
+     * @return {@code true} if the vibrator can play the given {@code effect} as intended,
+     *         {@code false} otherwise.
+     *
+     * @hide
+     */
+    public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
+        return effect.areVibrationFeaturesSupported(this);
+    }
+
+    /**
      * Query the estimated duration of given primitive.
      *
      * @param primitiveId Which primitives to query for.
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index 42b6c2da..a035092 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -23,6 +23,7 @@
 import android.os.Parcelable;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 import java.util.Objects;
 
@@ -77,8 +78,8 @@
 
     /** @hide */
     @Override
-    public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
-        if (vibrator.areAllEffectsSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+        if (vibratorInfo.isEffectSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
             return true;
         }
         if (!mFallback) {
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index c52a09c..95d97bf 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -22,7 +22,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 import com.android.internal.util.Preconditions;
 
@@ -77,8 +77,8 @@
 
     /** @hide */
     @Override
-    public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
-        return vibrator.areAllPrimitivesSupported(mPrimitiveId);
+    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+        return vibratorInfo.isPrimitiveSupported(mPrimitiveId);
     }
 
     /** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index e997bcd..5f9d102 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -20,7 +20,7 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 import com.android.internal.util.Preconditions;
 
@@ -96,7 +96,7 @@
 
     /** @hide */
     @Override
-    public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
         boolean areFeaturesSupported = true;
         // If the start/end frequencies are not the same, require frequency control since we need to
         // ramp up/down the frequency.
@@ -104,7 +104,7 @@
                 // If there is no frequency ramping, make sure that the one frequency used does not
                 // require frequency control.
                 || frequencyRequiresFrequencyControl(mStartFrequencyHz)) {
-            areFeaturesSupported &= vibrator.hasFrequencyControl();
+            areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
         }
         // If the start/end amplitudes are not the same, require amplitude control since we need to
         // ramp up/down the amplitude.
@@ -112,7 +112,7 @@
                 // If there is no amplitude ramping, make sure that the amplitude used does not
                 // require amplitude control.
                 || amplitudeRequiresAmplitudeControl(mStartAmplitude)) {
-            areFeaturesSupported &= vibrator.hasAmplitudeControl();
+            areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
         }
         return areFeaturesSupported;
     }
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index a585aa8..9576a5b 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -21,7 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 import com.android.internal.util.Preconditions;
 
@@ -82,13 +82,13 @@
 
     /** @hide */
     @Override
-    public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+    public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
         boolean areFeaturesSupported = true;
         if (frequencyRequiresFrequencyControl(mFrequencyHz)) {
-            areFeaturesSupported &= vibrator.hasFrequencyControl();
+            areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
         }
         if (amplitudeRequiresAmplitudeControl(mAmplitude)) {
-            areFeaturesSupported &= vibrator.hasAmplitudeControl();
+            areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
         }
         return areFeaturesSupported;
     }
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 3b286a7..17ac36f 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -21,7 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
 /**
  * Representation of a single segment of a {@link VibrationEffect}.
@@ -65,7 +65,7 @@
      *
      * @hide
      */
-    public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+    public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
 
     /**
      * Returns true if this segment could be a haptic feedback effect candidate.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4ecfc40..c6d8bd1 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,10 +16,12 @@
 
 package android.view;
 
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
 import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.InsetsControllerProto.CONTROL;
 import static android.view.InsetsControllerProto.STATE;
 import static android.view.InsetsSource.ID_IME;
+import static android.view.InsetsSource.ID_IME_CAPTION_BAR;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 import static android.view.WindowInsets.Type.FIRST;
 import static android.view.WindowInsets.Type.LAST;
@@ -40,6 +42,7 @@
 import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -652,6 +655,7 @@
     private int mLastWindowingMode;
     private boolean mStartingAnimation;
     private int mCaptionInsetsHeight = 0;
+    private int mImeCaptionBarInsetsHeight = 0;
     private boolean mAnimationsDisabled;
     private boolean mCompatSysUiVisibilityStaled;
 
@@ -693,6 +697,9 @@
                     if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
                         return;
                     }
+                    if (source1.getId() == ID_IME_CAPTION_BAR) {
+                        return;
+                    }
 
                     // Don't change the indexes of the sources while traversing. Remove it later.
                     mPendingRemoveIndexes.add(index1);
@@ -823,6 +830,9 @@
         if (mFrame.equals(frame)) {
             return;
         }
+        if (mImeCaptionBarInsetsHeight != 0) {
+            setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+        }
         mHost.notifyInsetsChanged();
         mFrame.set(frame);
     }
@@ -1007,6 +1017,12 @@
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            if (consumer.getId() == ID_IME_CAPTION_BAR) {
+                // The inset control for the IME caption bar will never be dispatched
+                // by the server.
+                continue;
+            }
+
             final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
             if (control != null) {
                 controllableTypes |= control.getType();
@@ -1499,7 +1515,8 @@
                 continue;
             }
             final InsetsSourceControl control = consumer.getControl();
-            if (control != null && control.getLeash() != null) {
+            if (control != null
+                    && (control.getLeash() != null || control.getId() == ID_IME_CAPTION_BAR)) {
                 controls.put(control.getId(), new InsetsSourceControl(control));
                 typesReady |= consumer.getType();
             }
@@ -1885,6 +1902,35 @@
     }
 
     @Override
+    public void setImeCaptionBarInsetsHeight(int height) {
+        if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+            return;
+        }
+        Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom);
+        InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR);
+        if (mImeCaptionBarInsetsHeight != height
+                || (source != null && !newFrame.equals(source.getFrame()))) {
+            mImeCaptionBarInsetsHeight = height;
+            if (mImeCaptionBarInsetsHeight != 0) {
+                mState.getOrCreateSource(ID_IME_CAPTION_BAR, captionBar())
+                        .setFrame(newFrame);
+                getSourceConsumer(ID_IME_CAPTION_BAR, captionBar()).setControl(
+                        new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(),
+                                null /* leash */, false /* initialVisible */,
+                                new Point(), Insets.NONE),
+                        new int[1], new int[1]);
+            } else {
+                mState.removeSource(ID_IME_CAPTION_BAR);
+                InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR);
+                if (sourceConsumer != null) {
+                    sourceConsumer.setControl(null, new int[1], new int[1]);
+                }
+            }
+            mHost.notifyInsetsChanged();
+        }
+    }
+
+    @Override
     public void setSystemBarsBehavior(@Behavior int behavior) {
         mHost.setSystemBarsBehavior(behavior);
     }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 6441186..ff009ed 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,6 +20,7 @@
 import static android.view.InsetsSourceProto.TYPE;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
 
 import android.annotation.IntDef;
@@ -47,6 +48,9 @@
 
     /** The insets source ID of IME */
     public static final int ID_IME = createId(null, 0, ime());
+    /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
+    static final int ID_IME_CAPTION_BAR =
+            InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
 
     /**
      * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
@@ -215,8 +219,12 @@
         // During drag-move and drag-resizing, the caption insets position may not get updated
         // before the app frame get updated. To layout the app content correctly during drag events,
         // we always return the insets with the corresponding height covering the top.
+        // However, with the "fake" IME navigation bar treated as a caption bar, we return the
+        // insets with the corresponding height the bottom.
         if (getType() == WindowInsets.Type.captionBar()) {
-            return Insets.of(0, frame.height(), 0, 0);
+            return getId() == ID_IME_CAPTION_BAR
+                    ? Insets.of(0, 0, 0, frame.height())
+                    : Insets.of(0, frame.height(), 0, 0);
         }
         // Checks for whether there is shared edge with insets for 0-width/height window.
         final boolean hasIntersection = relativeFrame.isEmpty()
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index e8f62fc..a4cbc52 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
     private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
             = new ArrayList<>();
     private int mCaptionInsetsHeight = 0;
+    private int mImeCaptionBarInsetsHeight = 0;
     private WindowInsetsAnimationControlListener mLoggingListener;
     private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
 
@@ -91,6 +92,11 @@
     }
 
     @Override
+    public void setImeCaptionBarInsetsHeight(int height) {
+        mImeCaptionBarInsetsHeight = height;
+    }
+
+    @Override
     public void setSystemBarsBehavior(int behavior) {
         if (mReplayedInsetsController != null) {
             mReplayedInsetsController.setSystemBarsBehavior(behavior);
@@ -168,6 +174,9 @@
         if (mCaptionInsetsHeight != 0) {
             controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
         }
+        if (mImeCaptionBarInsetsHeight != 0) {
+            controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+        }
         if (mAnimationsDisabled) {
             controller.setAnimationsDisabled(true);
         }
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index bc0bab7..cc2cd79 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -250,6 +250,16 @@
     void setCaptionInsetsHeight(int height);
 
     /**
+     * Sets the insets height for the IME caption bar, which corresponds to the
+     * "fake" IME navigation bar.
+     *
+     * @param height the insets height of the IME caption bar.
+     * @hide
+     */
+    default void setImeCaptionBarInsetsHeight(int height) {
+    }
+
+    /**
      * Controls the behavior of system bars.
      *
      * @param behavior Determines how the bars behave when being hidden by the application.
diff --git a/core/res/res/drawable/focus_event_rotary_input_background.xml b/core/res/res/drawable/focus_event_rotary_input_background.xml
new file mode 100644
index 0000000..512cd68
--- /dev/null
+++ b/core/res/res/drawable/focus_event_rotary_input_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:name="focus_event_rotary_input_background"
+       android:shape="rectangle">
+
+  <!-- View background color -->
+  <solid android:color="#80741b47" />
+
+  <!-- View border color and width -->
+  <stroke android:width="1dp" android:color="#ffff00ff" />
+
+  <!-- The radius makes the corners rounded -->
+  <corners android:radius="4dp" />
+
+</shape>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4614dce..a78b040 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5041,6 +5041,21 @@
     </array>
 
     <!-- See DisplayWhiteBalanceController.
+         A float array containing a list of ambient brightnesses, in Lux. This array,
+         together with config_displayWhiteBalanceLowLightAmbientBiasesStrong, is used to generate a
+         lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
+         ambient brightness readings to a bias, where the bias is used to linearly interpolate
+         between ambient color temperature and
+         config_displayWhiteBalanceLowLightAmbientColorTemperatureIdle.
+         This table is optional. If used, this array must,
+         1) Contain at least two entries
+         2) Be the same length as config_displayWhiteBalanceLowLightAmbientBiasesStrong. -->
+    <array name ="config_displayWhiteBalanceLowLightAmbientBrightnessesStrong">
+        <item>10.0</item>
+        <item>10.0</item>
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
          An array containing a list of biases. See
          config_displayWhiteBalanceLowLightAmbientBrightnesses for additional details.
          This array must be in the range of [0.0, 1.0]. -->
@@ -5050,12 +5065,28 @@
     </array>
 
     <!-- See DisplayWhiteBalanceController.
+         An array containing a list of biases. See
+         config_displayWhiteBalanceLowLightAmbientBrightnessesStrong for additional details.
+         This array must be in the range of [0.0, 1.0]. -->
+    <array name ="config_displayWhiteBalanceLowLightAmbientBiasesStrong">
+        <item>0.0</item>
+        <item>1.0</item>
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
          The ambient color temperature (in cct) to which we interpolate towards using the
          the look up table generated by config_displayWhiteBalanceLowLightAmbientBrightnesses
          and config_displayWhiteBalanceLowLightAmbientBiases. -->
     <item name="config_displayWhiteBalanceLowLightAmbientColorTemperature" format="float" type="dimen">6500.0</item>
 
     <!-- See DisplayWhiteBalanceController.
+         The ambient color temperature (in cct) to which we interpolate towards using the
+         the look up table generated by config_displayWhiteBalanceLowLightAmbientBrightnessesStrong
+         and config_displayWhiteBalanceLowLightAmbientBiasesStrong. Used when device is in Idle Screen
+         Brightness mode. -->
+    <item name="config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong" format="float" type="dimen">6500.0</item>
+
+    <!-- See DisplayWhiteBalanceController.
          A float array containing a list of ambient brightnesses, in Lux. This array,
          together with config_displayWhiteBalanceHighLightAmbientBiases, is used to generate a
          lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
@@ -5069,6 +5100,19 @@
     </array>
 
     <!-- See DisplayWhiteBalanceController.
+         A float array containing a list of ambient brightnesses, in Lux. This array,
+         together with config_displayWhiteBalanceHighLightAmbientBiasesStrong, is used to generate a
+         lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
+         ambient brightness readings to a bias, where the bias is used to linearly interpolate
+         between ambient color temperature and
+         config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong.
+         This table is optional. If used, this array must,
+         1) Contain at least two entries
+         2) Be the same length as config_displayWhiteBalanceHighLightAmbientBiasesStrong. -->
+    <array name ="config_displayWhiteBalanceHighLightAmbientBrightnessesStrong">
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
          An array containing a list of biases. See
          config_displayWhiteBalanceHighLightAmbientBrightnesses for additional details.
          This array must be in the range of [0.0, 1.0]. -->
@@ -5076,12 +5120,26 @@
     </array>
 
     <!-- See DisplayWhiteBalanceController.
+         An array containing a list of biases. See
+         config_displayWhiteBalanceHighLightAmbientBrightnessesStrong for additional details.
+         This array must be in the range of [0.0, 1.0]. -->
+    <array name ="config_displayWhiteBalanceHighLightAmbientBiasesStrong">
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
          The ambient color temperature (in cct) to which we interpolate towards using the
          the look up table generated by config_displayWhiteBalanceHighLightAmbientBrightnesses
          and config_displayWhiteBalanceHighLightAmbientBiases. -->
     <item name="config_displayWhiteBalanceHighLightAmbientColorTemperature" format="float" type="dimen">8000.0</item>
 
     <!-- See DisplayWhiteBalanceController.
+         The ambient color temperature (in cct) to which we interpolate towards using the
+         the look up table generated by config_displayWhiteBalanceHighLightAmbientBrightnessesStrong
+         and config_displayWhiteBalanceHighLightAmbientBiasesStrong. Used when device is in Idle
+         Screen Brightness mode. -->
+    <item name="config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong" format="float" type="dimen">8000.0</item>
+
+    <!-- See DisplayWhiteBalanceController.
          A float array containing a list of ambient color temperatures, in Kelvin. This array,
          together with config_displayWhiteBalanceDisplayColorTemperatures, is used to generate a
          lookup table used in DisplayWhiteBalanceController. This lookup table is used to map
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c5aa8b0..0dd6c74 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1895,22 +1895,22 @@
     <!-- Content description which should be used for the fingerprint icon. -->
     <string name="fingerprint_icon_content_description">Fingerprint icon</string>
 
+    <!-- Notification name shown when the system requires the user to set up device unlock. [CHAR LIMIT=NONE] -->
+    <string name="device_unlock_notification_name">Device unlock</string>
+    <!-- Notification title shown when the system suggests the user to set up another way to unlock. [CHAR LIMIT=NONE] -->
+    <string name="alternative_unlock_setup_notification_title">Try another way to unlock</string>
+    <!-- Notification content shown when the system suggests the user to enroll their face. [CHAR LIMIT=NONE] -->
+    <string name="alternative_face_setup_notification_content">Use Face Unlock when your fingerprint isn\'t recognized, like when your fingers are wet</string>
+    <!-- Notification content shown when the system suggests the user to enroll their fingerprint. [CHAR LIMIT=NONE] -->
+    <string name="alternative_fp_setup_notification_content">Use Fingerprint Unlock when your face isn\'t recognized, like when there\'s not enough light</string>
     <!-- Notification name shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_name">Face Unlock</string>
     <!-- Notification title shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_title">Issue with Face Unlock</string>
     <!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_content">Tap to delete your face model, then add your face again</string>
-    <!-- Title of a notification that directs the user to set up Face Unlock by enrolling their face. [CHAR LIMIT=NONE] -->
-    <string name="face_setup_notification_title">Set up Face Unlock</string>
-    <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
-    <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
     <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
     <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
-    <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
-    <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
-    <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
-    <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string>
 
     <!-- Notification name shown when the system requires the user to re-calibrate their fingerprint. [CHAR LIMIT=NONE] -->
     <string name="fingerprint_recalibrate_notification_name">Fingerprint Unlock</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c4cdd48..eaccd43 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2597,6 +2597,12 @@
   <!-- Biometric FRR config -->
   <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
 
+  <!-- Biometric FRR notification messages -->
+  <java-symbol type="string" name="device_unlock_notification_name" />
+  <java-symbol type="string" name="alternative_unlock_setup_notification_title" />
+  <java-symbol type="string" name="alternative_face_setup_notification_content" />
+  <java-symbol type="string" name="alternative_fp_setup_notification_content" />
+
   <!-- Device credential strings for BiometricManager -->
   <java-symbol type="string" name="screen_lock_app_setting_name" />
   <java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2637,8 +2643,6 @@
   <java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
   <java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
   <java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
-  <java-symbol type="string" name="fingerprint_setup_notification_title" />
-  <java-symbol type="string" name="fingerprint_setup_notification_content" />
   <java-symbol type="string" name="fingerprint_error_power_pressed" />
 
   <!-- Fingerprint config -->
@@ -2706,7 +2710,6 @@
   <java-symbol type="string" name="face_authenticated_no_confirmation_required" />
   <java-symbol type="string" name="face_authenticated_confirmation_required" />
   <java-symbol type="string" name="face_error_security_update_required" />
-  <java-symbol type="string" name="face_setup_notification_title" />
 
   <java-symbol type="string" name="config_biometric_prompt_ui_package" />
   <java-symbol type="array" name="config_biometric_sensors" />
@@ -4170,11 +4173,17 @@
   <java-symbol type="array" name="config_displayWhiteBalanceIncreaseThresholds" />
   <java-symbol type="array" name="config_displayWhiteBalanceDecreaseThresholds" />
   <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBrightnesses" />
+  <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBrightnessesStrong" />
   <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBiases" />
+  <java-symbol type="array" name="config_displayWhiteBalanceLowLightAmbientBiasesStrong" />
   <java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperature" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong" />
   <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBrightnesses" />
+  <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBrightnessesStrong" />
   <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBiases" />
+  <java-symbol type="array" name="config_displayWhiteBalanceHighLightAmbientBiasesStrong" />
   <java-symbol type="dimen" name="config_displayWhiteBalanceHighLightAmbientColorTemperature" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong" />
   <java-symbol type="array" name="config_displayWhiteBalanceAmbientColorTemperatures" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayColorTemperatures" />
   <java-symbol type="array" name="config_displayWhiteBalanceStrongAmbientColorTemperatures" />
@@ -5188,6 +5197,7 @@
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
 
   <java-symbol type="drawable" name="focus_event_pressed_key_background" />
+  <java-symbol type="drawable" name="focus_event_rotary_input_background" />
   <java-symbol type="string" name="config_defaultShutdownVibrationFile" />
   <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
 
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c14da29..7f56eb7 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -88,7 +88,7 @@
 
     resource_dirs: ["res"],
     resource_zips: [":FrameworksCoreTests_apks_as_resources"],
-    java_resources: [":ApkVerityTestCertDer"],
+    java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
 
     data: [
         ":BinderDeathRecipientHelperApp1",
diff --git a/core/tests/coretests/certs/Android.bp b/core/tests/coretests/certs/Android.bp
index 8d4ecf4..cefdc4d 100644
--- a/core/tests/coretests/certs/Android.bp
+++ b/core/tests/coretests/certs/Android.bp
@@ -13,3 +13,8 @@
     name: "FrameworksCoreTests_unit_test_cert",
     certificate: "unit_test",
 }
+
+filegroup {
+    name: "FrameworksCoreTests_unit_test_cert_der",
+    srcs: ["unit_test.der"],
+}
diff --git a/core/tests/coretests/certs/README b/core/tests/coretests/certs/README
index 00917a1..b5c096e 100644
--- a/core/tests/coretests/certs/README
+++ b/core/tests/coretests/certs/README
@@ -2,3 +2,5 @@
 
 development/tools/make_key unit_test         '/CN=unit_test'
 development/tools/make_key unit_test_diff    '/CN=unit_test_diff'
+
+openssl x509 -in unit_test.x509.pem -out unit_test.der -outform der
diff --git a/core/tests/coretests/certs/unit_test.der b/core/tests/coretests/certs/unit_test.der
new file mode 100644
index 0000000..4dbbc49
--- /dev/null
+++ b/core/tests/coretests/certs/unit_test.der
Binary files differ
diff --git a/core/tests/coretests/res/raw/fsverity_sig b/core/tests/coretests/res/raw/fsverity_sig
index b2f335d..2c28f0b 100644
--- a/core/tests/coretests/res/raw/fsverity_sig
+++ b/core/tests/coretests/res/raw/fsverity_sig
Binary files differ
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
index 1513654..a978e3b 100644
--- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -229,11 +229,12 @@
     @Test
     public void testSignatureGeneratedExternally() throws Exception {
         var context = InstrumentationRegistry.getInstrumentation().getContext();
-        byte[] cert = getClass().getClassLoader().getResourceAsStream("ApkVerityTestCert.der")
+        byte[] cert = getClass().getClassLoader().getResourceAsStream("unit_test.der")
                 .readAllBytes();
         // The signature is generated by:
-        //   fsverity sign <(echo -n fs-verity) fsverity_sig --key=ApkVerityTestKey.pem \
-        //   --cert=ApkVerityTestCert.pem
+        //   openssl pkcs8 -topk8 -nocrypt -in certs/unit_test.pk8 -out certs/unit_test.key.pem
+        //   fsverity sign <(echo -n fs-verity) fsverity_sig --key=certs/unit_test.key.pem \
+        //   --cert=certs/unit_test.x509.pem
         byte[] sig = context.getResources().openRawResource(R.raw.fsverity_sig).readAllBytes();
         // The fs-verity digest is generated by:
         //   fsverity digest --compact <(echo -n fs-verity)
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 8be489e..e875875 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -41,8 +41,6 @@
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.internal.R;
 
 import org.jetbrains.annotations.NotNull;
@@ -838,31 +836,29 @@
 
     @Test
     public void testAreVibrationFeaturesSupported_allSegmentsSupported() {
-        Vibrator vibrator =
-                createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                        .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
-                        .build());
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+                .build();
 
         assertTrue(VibrationEffect.createWaveform(
                         /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(VibrationEffect.createWaveform(
                         /* timings= */ new long[] {1, 2, 3},
                         /* amplitudes= */ new int[] {10, 20, 40},
                         /* repeatIndex= */ 2)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(
                 VibrationEffect.startComposition()
                         .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
                         .repeatEffectIndefinitely(TEST_ONE_SHOT)
                         .compose()
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testAreVibrationFeaturesSupported_withUnsupportedSegments() {
-        Vibrator vibrator =
-                createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1).build());
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
 
         assertFalse(
                 VibrationEffect.startComposition()
@@ -872,7 +868,7 @@
                                 /* amplitudes= */ new int[] {10, 20, 40},
                                 /* repeatIndex= */ -1))
                         .compose()
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -996,13 +992,4 @@
 
         return context;
     }
-
-    private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
-        return new SystemVibrator(InstrumentationRegistry.getContext()) {
-            @Override
-            public VibratorInfo getInfo() {
-                return info;
-            }
-        };
-    }
 }
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index ff917aa..808c4ec 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -57,6 +57,17 @@
     }
 
     @Test
+    public void testHasFrequencyControl() {
+        VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
+        assertFalse(noCapabilities.hasFrequencyControl());
+        VibratorInfo composeAndFrequencyControl = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
+                .setCapabilities(
+                        IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+                .build();
+        assertTrue(composeAndFrequencyControl.hasFrequencyControl());
+    }
+
+    @Test
     public void testHasCapabilities() {
         VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
                 .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
@@ -335,4 +346,186 @@
         assertEquals(original, restored);
         assertEquals(original.hashCode(), restored.hashCode());
     }
+
+    @Test
+    public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudeUnsupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+        // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
+        assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
+        assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudeSupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+        // All amplitudes are min, max, or default. Requires no amplitude control.
+        assertTrue(info.areVibrationFeaturesSupported(
+                waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createWaveform(
+                        /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_withAmplitudeControl_allWaveformsSupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+                .build();
+
+        // All forms of amplitudes are valid when amplitude control is available.
+        assertTrue(info.areVibrationFeaturesSupported(
+                waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createWaveform(
+                        /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+        assertTrue(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                waveformWithAmplitudes(7, 255, 0, 0, 60)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_compositionsWithSupportedPrimitivesSupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+                .build();
+
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose()));
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(
+                                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                                /* scale= */ 0.2f,
+                                /* delay= */ 200)
+                        .compose()));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_compositionsWithUnupportedPrimitivesUnsupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+                .build();
+
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                        .compose()));
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+                        .compose()));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+                .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+                .build();
+
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addEffect(VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {1, 2, 3},
+                                /* amplitudes= */ new int[] {10, 20, 255},
+                                /* repeatIndex= */ -1))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+                        .compose()));
+
+        info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+                .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
+                .build();
+
+        assertTrue(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                        .addEffect(VibrationEffect.createWaveform(
+                                // These timings are given either 0 or default amplitudes, which
+                                // do not require vibrator's amplitude control.
+                                /* timings= */ new long[] {1, 2, 3},
+                                /* repeatIndex= */ -1))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .compose()));
+    }
+
+    @Test
+    public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
+        VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+                .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+                .build();
+
+        // Not supported due to the TICK primitive, which the vibrator has no support for.
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {1, 2, 3},
+                                /* amplitudes= */ new int[] {10, 20, 255},
+                                /* repeatIndex= */ -1))
+                        .compose()));
+        // Not supported due to the THUD effect, which the vibrator has no support for.
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addEffect(VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {1, 2, 3},
+                                /* amplitudes= */ new int[] {10, 20, 255},
+                                /* repeatIndex= */ -1))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
+                        .compose()));
+
+        info = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+                .setSupportedEffects(VibrationEffect.EFFECT_POP)
+                .build();
+
+        // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
+        // DEFAULT), because the vibrator has no amplitude control.
+        assertFalse(info.areVibrationFeaturesSupported(
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                        .addEffect(VibrationEffect.createWaveform(
+                                /* timings= */ new long[] {1, 2, 3},
+                                /* amplitudes= */ new int[] {10, 20, 255},
+                                /* repeatIndex= */ -1))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+                        .compose()));
+    }
+
+    private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
+        long[] timings = new long[amplitudes.length];
+        for (int i = 0; i < timings.length; i++) {
+            timings[i] = i * 2; // Arbitrary timings.
+        }
+        return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
+    }
 }
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index c559e34..8141ca4 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -578,189 +578,6 @@
         assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
     }
 
-    @Test
-    public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudes() {
-        Vibrator vibrator =
-                createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                        .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                        .setSupportedEffects(VibrationEffect.EFFECT_THUD)
-                        .build());
-
-        // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
-        assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
-        assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudes() {
-        Vibrator vibrator =
-                createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                        .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                        .setSupportedEffects(VibrationEffect.EFFECT_THUD)
-                        .build());
-
-        // All amplitudes are min, max, or default. Requires no amplitude control.
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createWaveform(
-                        /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_withAmplitudeControl() {
-        Vibrator vibrator =
-                createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                        .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
-                        .build());
-
-        // All forms of amplitudes are valid when amplitude control is available.
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createWaveform(
-                        /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                waveformWithAmplitudes(7, 255, 0, 0, 60)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_primitiveCompositionsWithSupportedPrimitives() {
-        Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
-                .build());
-
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .compose()));
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(
-                                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                /* scale= */ 0.2f,
-                                /* delay= */ 200)
-                        .compose()));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_primitiveCompositionsWithUnupportedPrimitives() {
-        Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
-                .build());
-
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
-                        .compose()));
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
-                        .compose()));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
-        Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
-                .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
-                .build());
-
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addEffect(VibrationEffect.createWaveform(
-                                /* timings= */ new long[] {1, 2, 3},
-                                /* amplitudes= */ new int[] {10, 20, 255},
-                                /* repeatIndex= */ -1))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
-                        .compose()));
-
-        vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
-                .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
-                .build());
-
-        assertTrue(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
-                        .addEffect(VibrationEffect.createWaveform(
-                                // These timings are given either 0 or default amplitudes, which
-                                // do not require vibrator's amplitude control.
-                                /* timings= */ new long[] {1, 2, 3},
-                                /* repeatIndex= */ -1))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                        .compose()));
-    }
-
-    @Test
-    public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
-        Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
-                .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
-                .build());
-
-        // Not supported due to the TICK primitive, which the vibrator has no support for.
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .addEffect(VibrationEffect.createWaveform(
-                                /* timings= */ new long[] {1, 2, 3},
-                                /* amplitudes= */ new int[] {10, 20, 255},
-                                /* repeatIndex= */ -1))
-                        .compose()));
-        // Not supported due to the THUD effect, which the vibrator has no support for.
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addEffect(VibrationEffect.createWaveform(
-                                /* timings= */ new long[] {1, 2, 3},
-                                /* amplitudes= */ new int[] {10, 20, 255},
-                                /* repeatIndex= */ -1))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
-                        .compose()));
-
-        vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
-                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
-                .setSupportedEffects(VibrationEffect.EFFECT_POP)
-                .build());
-
-        // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
-        // DEFAULT), because the vibrator has no amplitude control.
-        assertFalse(vibrator.areVibrationFeaturesSupported(
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
-                        .addEffect(VibrationEffect.createWaveform(
-                                /* timings= */ new long[] {1, 2, 3},
-                                /* amplitudes= */ new int[] {10, 20, 255},
-                                /* repeatIndex= */ -1))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
-                        .compose()));
-    }
-
     /**
      * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
      */
@@ -768,21 +585,4 @@
         assertTrue(info.getFrequencyProfile().isEmpty());
         assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
     }
-
-    private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
-        return new SystemVibrator(mContextSpy) {
-            @Override
-            public VibratorInfo getInfo() {
-                return info;
-            }
-        };
-    }
-
-    private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
-        long[] timings = new long[amplitudes.length];
-        for (int i = 0; i < timings.length; i++) {
-            timings[i] = i * 2; // Arbitrary timings.
-        }
-        return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
-    }
 }
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 8268077..4f5f3c0 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -25,18 +25,14 @@
 import static org.testng.Assert.assertThrows;
 
 import android.os.Parcel;
-import android.os.SystemVibrator;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
 
-import androidx.test.InstrumentationRegistry;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
 public class PrebakedSegmentTest {
 
     @Test
@@ -149,121 +145,121 @@
 
     @Test
     public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_vibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(
                 VibrationEffect.EFFECT_TICK,
                 VibrationEffect.EFFECT_CLICK,
                 VibrationEffect.EFFECT_DOUBLE_CLICK,
                 VibrationEffect.EFFECT_HEAVY_CLICK);
 
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
 
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_noVibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
 
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_vibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(
                 VibrationEffect.EFFECT_TICK,
                 VibrationEffect.EFFECT_CLICK,
                 VibrationEffect.EFFECT_DOUBLE_CLICK,
                 VibrationEffect.EFFECT_HEAVY_CLICK);
 
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_noVibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
 
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_vibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(
                 VibrationEffect.EFFECT_THUD,
                 VibrationEffect.EFFECT_POP,
                 VibrationEffect.EFFECT_TEXTURE_TICK);
 
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_noVibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
 
         assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_vibratorSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(
                 VibrationEffect.EFFECT_THUD,
                 VibrationEffect.EFFECT_POP,
                 VibrationEffect.EFFECT_TEXTURE_TICK);
 
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_noVibSupport() {
-        Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+        VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
 
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
         assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
-                .areVibrationFeaturesSupported(vibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -283,14 +279,9 @@
         return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }
 
-    private static Vibrator createVibratorWithSupportedEffects(int... supportedEffects) {
-        return new SystemVibrator(InstrumentationRegistry.getContext()) {
-            @Override
-            public VibratorInfo getInfo() {
-                return new VibratorInfo.Builder(/* id= */ 1)
-                        .setSupportedEffects(supportedEffects)
-                        .build();
-            }
-        };
+    private static VibratorInfo createVibratorInfoWithSupportedEffects(int... supportedEffects) {
+        return new VibratorInfo.Builder(/* id= */ 1)
+                .setSupportedEffects(supportedEffects)
+                .build();
     }
 }
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 6f5adcd..ec5a084 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -25,18 +25,14 @@
 
 import android.hardware.vibrator.IVibrator;
 import android.os.Parcel;
-import android.os.SystemVibrator;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
 
-import androidx.test.InstrumentationRegistry;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
 public class PrimitiveSegmentTest {
     private static final float TOLERANCE = 1e-2f;
 
@@ -146,15 +142,15 @@
     public void testVibrationFeaturesSupport_primitiveSupportedByVibrator() {
         assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_CLICK)));
         assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_THUD)));
         assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
     }
 
@@ -162,15 +158,15 @@
     public void testVibrationFeaturesSupport_primitiveNotSupportedByVibrator() {
         assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_THUD)));
         assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_CLICK)));
         assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
                 .areVibrationFeaturesSupported(
-                        createVibratorWithSupportedPrimitive(
+                        createVibratorInfoWithSupportedPrimitive(
                                 VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
     }
 
@@ -193,15 +189,10 @@
         return new PrimitiveSegment(primitiveId, 0.2f, 10);
     }
 
-    private static Vibrator createVibratorWithSupportedPrimitive(int primitiveId) {
-        return new SystemVibrator(InstrumentationRegistry.getContext()) {
-            @Override
-            public VibratorInfo getInfo() {
-                return new VibratorInfo.Builder(/* id= */ 1)
-                        .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
-                        .setSupportedPrimitive(primitiveId, 10)
-                        .build();
-                }
-        };
+    private static VibratorInfo createVibratorInfoWithSupportedPrimitive(int primitiveId) {
+        return new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+                .setSupportedPrimitive(primitiveId, 10)
+                .build();
     }
 }
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 68870e5..5caa86b 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -23,31 +23,21 @@
 import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.hardware.vibrator.IVibrator;
 import android.os.Parcel;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
 public class RampSegmentTest {
     private static final float TOLERANCE = 1e-2f;
 
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock
-    private Vibrator mVibrator;
-
     @Test
     public void testCreation() {
         RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -147,71 +137,71 @@
 
     @Test
     public void testVibrationFeaturesSupport_amplitudeAndFrequencyControls_supported() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
 
         // Increasing amplitude
-        assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+        assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
         // Increasing frequency
-        assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(mVibrator));
+        assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(info));
         // Decreasing amplitude
-        assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+        assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
         // Decreasing frequency
-        assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(mVibrator));
+        assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(info));
         // Zero duration
-        assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(mVibrator));
+        assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_noAmplitudeControl_unsupportedForChangingAmplitude() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
 
         // Test with increasing/decreasing amplitudes.
-        assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+        assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_noAmplitudeControl_fractionalAmplitudeUnsupported() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
 
-        assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+        assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_unchangingZeroAmplitude_supported() {
         RampSegment amplitudeZeroWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
         RampSegment amplitudeZeroWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
 
-        assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
 
-        assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_unchangingOneAmplitude_supported() {
         RampSegment amplitudeOneWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
         RampSegment amplitudeOneWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
 
-        assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
 
-        assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -220,52 +210,52 @@
                 new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.5f, 0.8f, 10);
         RampSegment defaultAmplitudeDecreasingFrequency =
                 new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.8f, 0.5f, 10);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
 
-        assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
 
-        assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+        assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_noFrequencyControl_unsupportedForChangingFrequency() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
-        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
 
         // Test with increasing/decreasing frequencies.
-        assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+        assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_noFrequencyControl_fractionalFrequencyUnsupported() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
-        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
 
-        assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(mVibrator));
-        assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+        assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(info));
+        assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_unchangingZeroFrequency_supported() {
         RampSegment frequencyZeroWithIncreasingAmplitude = new RampSegment(0.1f, 1, 0, 0, 10);
         RampSegment frequencyZeroWithDecreasingAmplitude = new RampSegment(1, 0.1f, 0, 0, 10);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
-        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        VibratorInfo info =
+                createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
 
-        assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+        assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
 
-        assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
-        assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+        assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -274,4 +264,17 @@
         // duration checked in VibrationEffect implementations.
         assertTrue(new RampSegment(0.5f, 1, 0, 0, 5_000).isHapticFeedbackCandidate());
     }
+
+    private static VibratorInfo createVibInfo(
+            boolean hasAmplitudeControl, boolean hasFrequencyControl) {
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+        long capabilities = 0;
+        if (hasAmplitudeControl) {
+            capabilities |= IVibrator.CAP_AMPLITUDE_CONTROL;
+        }
+        if (hasFrequencyControl) {
+            capabilities |= (IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        }
+        return builder.setCapabilities(capabilities).build();
+    }
 }
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 34bb892..44db306 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -21,31 +21,20 @@
 import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.hardware.vibrator.IVibrator;
 import android.os.Parcel;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
 public class StepSegmentTest {
     private static final float TOLERANCE = 1e-2f;
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock
-    private Vibrator mVibrator;
-
     @Test
     public void testCreation() {
         StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequencyHz= */ 1f,
@@ -160,26 +149,26 @@
     public void testVibrationFeaturesSupport_zeroAmplitude_supported() {
         StepSegment segment =
                 new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 0);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_maxAmplitude_supported() {
         StepSegment segment =
                 new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 0);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -189,60 +178,60 @@
                         /* amplitude= */ VibrationEffect.DEFAULT_AMPLITUDE,
                         /* frequencyHz= */ 0,
                         /* duration= */ 0);
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_fractionalAmplitude_hasAmplitudeCtrl_supported() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+        VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
 
         assertTrue(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
-                .areVibrationFeaturesSupported(mVibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_fractionalAmplitude_hasNoAmplitudeCtrl_notSupported() {
-        when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+        VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
 
         assertFalse(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
-                .areVibrationFeaturesSupported(mVibrator));
+                .areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_zeroFrequency_supported() {
         StepSegment segment =
                 new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0, /* duration= */ 0);
-        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
 
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_nonZeroFrequency_hasFrequencyCtrl_supported() {
         StepSegment segment =
                 new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
-        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
 
-        assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+        assertTrue(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
     public void testVibrationFeaturesSupport_nonZeroFrequency_hasNoFrequencyCtrl_notSupported() {
         StepSegment segment =
                 new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
-        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
 
-        assertFalse(segment.areVibrationFeaturesSupported(mVibrator));
+        assertFalse(segment.areVibrationFeaturesSupported(info));
     }
 
     @Test
@@ -251,4 +240,21 @@
         // duration checked in VibrationEffect implementations.
         assertTrue(new StepSegment(0, 0, 5_000).isHapticFeedbackCandidate());
     }
+
+    private static VibratorInfo createVibInfoForAmplitude(boolean hasAmplitudeControl) {
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+        if (hasAmplitudeControl) {
+            builder.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        }
+        return builder.build();
+    }
+
+    private static VibratorInfo createVibInfoForFrequency(boolean hasFrequencyControl) {
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+        if (hasFrequencyControl) {
+            builder.setCapabilities(
+                    IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        }
+        return builder.build();
+    }
 }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index ef062df..4b10b56 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -23,14 +23,16 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentProvider;
-import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -44,8 +46,8 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
 
-import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
@@ -77,13 +79,14 @@
 
     @Mock private Context mContext;
     @Mock private Resources mResources;
-    @Mock private ContentResolver mContentResolver;
     @Mock private AudioManager mAudioManager;
     @Mock private TelephonyManager mTelephonyManager;
 
+    @Mock private MockContentResolver mContentResolver;
+    private MockSettingsProvider mSettingsProvider;
+
     @Before
     public void setUp() {
-        clearLongPressPowerValues();
         MockitoAnnotations.initMocks(this);
         when(mContext.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManager);
         when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))).thenReturn(
@@ -91,14 +94,20 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getApplicationContext()).thenReturn(mContext);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
-        when(mContext.getContentResolver()).thenReturn(getContentResolver());
 
         mSettingsHelper = spy(new SettingsHelper(mContext));
+        mContentResolver = spy(new MockContentResolver());
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        mSettingsProvider = new MockSettingsProvider(mContext);
+        mContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
     }
 
     @After
     public void tearDown() {
-        clearLongPressPowerValues();
+        Settings.Global.putString(mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                null);
+        Settings.Global.putString(mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                null);
     }
 
     @Test
@@ -123,33 +132,30 @@
         mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
                 SETTING_KEY, SETTING_VALUE, /* restoredFromSdkInt */ 0);
 
-        verifyZeroInteractions(mContentResolver);
+        // The only time of interaction happened during setUp()
+        verify(mContentResolver, times(1))
+                .addProvider(Settings.AUTHORITY, mSettingsProvider);
+
+        verifyNoMoreInteractions(mContentResolver);
     }
 
     @Test
     public void testRestoreValue_lppForAssistantEnabled_updatesValue() {
-        ContentResolver cr =
-                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getContentResolver();
         when(mResources.getBoolean(
                 R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
                 true);
 
-        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+        mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
                 Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
 
-        assertThat(
-                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
-                    .isEqualTo(5);
-        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
-                -1)).isEqualTo(2);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(5);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(2);
     }
 
     @Test
     public void testRestoreValue_lppForAssistantNotEnabled_updatesValueToDefaultConfig() {
-        ContentResolver cr =
-                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getContentResolver();
         when(mResources.getBoolean(
                 R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
                 true);
@@ -161,21 +167,17 @@
                 R.integer.config_keyChordPowerVolumeUp)).thenReturn(
                 1);
 
-        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+        mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
                 Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
 
-        assertThat(
-                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
-                .isEqualTo(1);
-        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
-                -1)).isEqualTo(1);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(1);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(1);
     }
 
     @Test
     public void testRestoreValue_lppForAssistantNotEnabledDefaultConfig_updatesValue() {
-        ContentResolver cr =
-                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getContentResolver();
         when(mResources.getBoolean(
                 R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
                 true);
@@ -187,47 +189,39 @@
                 R.integer.config_keyChordPowerVolumeUp)).thenReturn(
                 1);
 
-        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+        mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
                 Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
 
-        assertThat(
-                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
-                    .isEqualTo(1);
-        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
-                -1)).isEqualTo(1);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1)).isEqualTo(1);
+        assertThat(Settings.Global.getInt(
+                mContentResolver, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, -1)).isEqualTo(1);
     }
 
     @Test
     public void testRestoreValue_lppForAssistantNotAvailable_doesNotRestore() {
-        ContentResolver cr =
-                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getContentResolver();
-        when(mResources.getBoolean(
-                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
-                false);
+        when(mResources.getBoolean(R.bool.config_longPressOnPowerForAssistantSettingAvailable))
+                .thenReturn(false);
 
-        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
-                Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
+        mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "500", 0);
 
-        assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
-                -1))).isEqualTo(-1);
+        assertThat((Settings.Global.getInt(
+                mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))).isEqualTo(-1);
     }
 
 
     @Test
     public void testRestoreValue_lppForAssistantInvalid_doesNotRestore() {
-        ContentResolver cr =
-                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getContentResolver();
         when(mResources.getBoolean(
                 R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
                 false);
 
-        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+        mSettingsHelper.restoreValue(mContext, mContentResolver, new ContentValues(), Uri.EMPTY,
                 Settings.Global.POWER_BUTTON_LONG_PRESS, "trees", 0);
 
-        assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
-                -1))).isEqualTo(-1);
+        assertThat((Settings.Global.getInt(
+                mContentResolver, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))).isEqualTo(-1);
     }
 
     @Test
@@ -363,9 +357,6 @@
         final String newRingtoneValueCanonicalized =
                 "content://media/internal/audio/media/100?title=Song&canonical=1";
 
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         ContentProvider mockMediaContentProvider =
                 new MockContentProvider(mContext) {
                     @Override
@@ -386,25 +377,22 @@
                     }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+        resetRingtoneSettingsToDefault();
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(DEFAULT_RINGTONE_VALUE);
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.RINGTONE,
                 sourceRingtoneValue,
                 0);
 
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(newRingtoneValueCanonicalized);
     }
 
@@ -417,9 +405,6 @@
         final String newRingtoneValueCanonicalized =
                 "content://0@media/external/audio/media/100?title=Song&canonical=1";
 
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
         cursor.addRow(new Object[] {100L});
 
@@ -458,24 +443,21 @@
                     }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
+        resetRingtoneSettingsToDefault();
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.RINGTONE,
                 sourceRingtoneValue,
                 0);
 
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(newRingtoneValueCanonicalized);
     }
 
@@ -488,9 +470,6 @@
         final String newRingtoneValueCanonicalized =
                 "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
 
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
         cursor.addRow(new Object[] {200L});
 
@@ -529,17 +508,14 @@
                     }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
+        resetRingtoneSettingsToDefault();
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.NOTIFICATION_SOUND,
@@ -548,7 +524,7 @@
 
         assertThat(
                         Settings.System.getString(
-                                mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+                                mContentResolver, Settings.System.NOTIFICATION_SOUND))
                 .isEqualTo(newRingtoneValueCanonicalized);
     }
 
@@ -561,9 +537,6 @@
         final String newRingtoneValueCanonicalized =
                 "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
 
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
         cursor.addRow(new Object[] {300L});
 
@@ -600,26 +573,29 @@
                         assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
                         return cursor;
                     }
+
+                    @Override
+                    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType,
+                            Bundle opts) {
+                        return null;
+                    }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
+        resetRingtoneSettingsToDefault();
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.ALARM_ALERT,
                 sourceRingtoneValue,
                 0);
 
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
                 .isEqualTo(newRingtoneValueCanonicalized);
     }
 
@@ -628,9 +604,6 @@
         final String sourceRingtoneValue =
                 "content://0@media/external/audio/media/1?title=Song&canonical=1";
 
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         // This is to mock the case that there are multiple results by querying title +
         // ringtone_type.
         MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
@@ -651,32 +624,26 @@
                     }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
+        resetRingtoneSettingsToDefault();
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.RINGTONE,
                 sourceRingtoneValue,
                 0);
 
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(DEFAULT_RINGTONE_VALUE);
     }
 
     @Test
     public void testRestoreValue_customRingtone_restoreSilentValue() {
-        MockContentResolver mMockContentResolver = new MockContentResolver();
-        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
-
         ContentProvider mockMediaContentProvider =
                 new MockContentProvider(mContext) {
                     @Override
@@ -691,37 +658,46 @@
                     }
                 };
 
-        ContentProvider mockSettingsContentProvider =
-                new MockSettingsProvider(mContext, getContentResolver());
-        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
-        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
 
-        resetRingtoneSettingsToDefault(mMockContentResolver);
+        resetRingtoneSettingsToDefault();
 
         mSettingsHelper.restoreValue(
                 mContext,
-                mMockContentResolver,
+                mContentResolver,
                 new ContentValues(),
                 Uri.EMPTY,
                 Settings.System.RINGTONE,
                 "_silent",
                 0);
 
-        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(null);
     }
 
-    public static class MockSettingsProvider extends MockContentProvider {
-        ContentResolver mBaseContentResolver;
-
-        public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+    private static class MockSettingsProvider extends MockContentProvider {
+        private final ArrayMap<String, String> mKeyValueStore = new ArrayMap<>();
+        MockSettingsProvider(Context context) {
             super(context);
-            this.mBaseContentResolver = baseContentResolver;
         }
 
         @Override
         public Bundle call(String method, String request, Bundle args) {
-            return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+            if (method.startsWith("PUT_")) {
+                mKeyValueStore.put(request, args.getString("value"));
+                return null;
+            } else if (method.startsWith("GET_")) {
+                return Bundle.forPair("value", mKeyValueStore.getOrDefault(request, ""));
+            }
+            return null;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            String name = values.getAsString("name");
+            String value = values.getAsString("value");
+            mKeyValueStore.put(name, value);
+            return null;
         }
     }
 
@@ -752,15 +728,13 @@
     }
 
     private int getAutoRotationSettingValue() {
-        return Settings.System.getInt(
-                getContentResolver(),
+        return Settings.System.getInt(mContentResolver,
                 Settings.System.ACCELEROMETER_ROTATION,
                 /* default= */ -1);
     }
 
     private void setAutoRotationSettingValue(int value) {
-        Settings.System.putInt(
-                getContentResolver(),
+        Settings.System.putInt(mContentResolver,
                 Settings.System.ACCELEROMETER_ROTATION,
                 value
         );
@@ -769,7 +743,7 @@
     private void restoreAutoRotationSetting(int newValue) {
         mSettingsHelper.restoreValue(
                 mContext,
-                getContentResolver(),
+                mContentResolver,
                 new ContentValues(),
                 /* destination= */ Settings.System.CONTENT_URI,
                 /* name= */ Settings.System.ACCELEROMETER_ROTATION,
@@ -777,31 +751,19 @@
                 /* restoredFromSdkInt= */ 0);
     }
 
-    private ContentResolver getContentResolver() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext()
-                .getContentResolver();
-    }
-
-    private void clearLongPressPowerValues() {
-        ContentResolver cr = InstrumentationRegistry.getInstrumentation().getTargetContext()
-                .getContentResolver();
-        Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
-        Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
-    }
-
-    private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+    private void resetRingtoneSettingsToDefault() {
         Settings.System.putString(
-                contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+                mContentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
         Settings.System.putString(
-                contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+                mContentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
         Settings.System.putString(
-                contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+                mContentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
 
-        assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.RINGTONE))
                 .isEqualTo(DEFAULT_RINGTONE_VALUE);
-        assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.NOTIFICATION_SOUND))
                 .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
-        assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+        assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
                 .isEqualTo(DEFAULT_ALARM_VALUE);
     }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e539c95..e7a53e5b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -62,6 +62,7 @@
     private val burmeseLineSpacing =
         resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
     private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+    protected var onSecondaryDisplay: Boolean = false
 
     override val events: DefaultClockEvents
     override val config = ClockConfig()
@@ -142,6 +143,11 @@
                     view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
                     recomputePadding(targetRegion)
                 }
+
+                override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
+                    this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
+                    recomputePadding(null)
+                }
             }
 
         open fun recomputePadding(targetRegion: Rect?) {}
@@ -182,13 +188,19 @@
         override fun recomputePadding(targetRegion: Rect?) {
             // We center the view within the targetRegion instead of within the parent
             // view by computing the difference and adding that to the padding.
-            val parent = view.parent
-            val yDiff =
-                if (targetRegion != null && parent is View && parent.isLaidOut())
-                    targetRegion.centerY() - parent.height / 2f
-                else 0f
             val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
+            lp.topMargin =
+                if (onSecondaryDisplay) {
+                    // On the secondary display we don't want any additional top/bottom margin.
+                    0
+                } else {
+                    val parent = view.parent
+                    val yDiff =
+                        if (targetRegion != null && parent is View && parent.isLaidOut())
+                            targetRegion.centerY() - parent.height / 2f
+                        else 0f
+                    (-0.5f * view.bottom + yDiff).toInt()
+                }
             view.setLayoutParams(lp)
         }
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index d962732..527f8007 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -177,6 +177,9 @@
      * targetRegion is relative to the parent view.
      */
     fun onTargetRegionChanged(targetRegion: Rect?)
+
+    /** Called to notify the clock about its display. */
+    fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
 }
 
 /** Tick rates for clocks */
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
new file mode 100644
index 0000000..593f507f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/presentation"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.keyguard.KeyguardStatusView
+        android:id="@+id/clock"
+        android:layout_width="410dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical">
+
+        <include
+            android:id="@+id/keyguard_clock_container"
+            layout="@layout/keyguard_clock_switch"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </com.android.keyguard.KeyguardStatusView>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 12f13e9..3a15ae4 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -127,6 +127,8 @@
         android:gravity="center_vertical"
         android:paddingStart="@dimen/shade_header_system_icons_padding_start"
         android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
+        android:paddingTop="@dimen/shade_header_system_icons_padding_top"
+        android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/privacy_container"
         app:layout_constraintTop_toTopOf="@id/clock">
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 9af46c5..3796415 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -64,8 +64,7 @@
             android:layout_height="wrap_content"
             android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
             style="@style/TextAppearance.Dialog.Body.Message"
-            android:gravity="start"
-            android:lineHeight="@dimen/screenrecord_warning_line_height"/>
+            android:gravity="start"/>
 
         <!-- Buttons -->
         <LinearLayout
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index d85e012..915dcdb 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -82,6 +82,8 @@
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
     <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
     <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
+    <dimen name="shade_header_system_icons_padding_top">2dp</dimen>
+    <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8310b95..2dc1b45 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -496,9 +496,10 @@
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
     <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
-    <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
     <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
     <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_top">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen>
 
     <!-- The top margin of the panel that holds the list of notifications.
          On phones it's always 0dp but it's overridden in Car UI
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 2ec6180..fe61c46 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -56,7 +56,7 @@
     <Constraint android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
+            android:layout_height="wrap_content"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/privacy_container"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
new file mode 100644
index 0000000..899cad89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.app.Presentation
+import android.content.Context
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** [Presentation] shown in connected displays while on keyguard. */
+class ConnectedDisplayKeyguardPresentation
+@AssistedInject
+constructor(
+    @Assisted display: Display,
+    context: Context,
+    private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+) :
+    Presentation(
+        context,
+        display,
+        R.style.Theme_SystemUI_KeyguardPresentation,
+        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+    ) {
+
+    private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+    private lateinit var clock: KeyguardStatusView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(
+            LayoutInflater.from(context)
+                .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
+        )
+        val window = window ?: error("no window available.")
+
+        // Logic to make the lock screen fullscreen
+        window.decorView.systemUiVisibility =
+            (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
+        window.attributes.fitInsetsTypes = 0
+        window.isNavigationBarContrastEnforced = false
+        window.navigationBarColor = Color.TRANSPARENT
+
+        clock = findViewById(R.id.clock)
+        keyguardStatusViewController =
+            keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply {
+                setDisplayedOnSecondaryDisplay()
+                init()
+            }
+    }
+
+    /** [ConnectedDisplayKeyguardPresentation] factory. */
+    @AssistedFactory
+    interface Factory {
+        /** Creates a new [Presentation] for the given [display]. */
+        fun create(
+            display: Display,
+        ): ConnectedDisplayKeyguardPresentation
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 30b8ed0..b589887 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,10 +1,5 @@
 package com.android.keyguard;
 
-import static android.view.View.ALPHA;
-import static android.view.View.SCALE_X;
-import static android.view.View.SCALE_Y;
-import static android.view.View.TRANSLATION_Y;
-
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
@@ -149,6 +144,13 @@
         updateStatusArea(/* animate= */false);
     }
 
+    /** Sets whether the large clock is being shown on a connected display. */
+    public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
+        if (mClock != null) {
+            mClock.getLargeClock().getEvents().onSecondaryDisplayChanged(onSecondaryDisplay);
+        }
+    }
+
     /**
      * Enable or disable split shade specific positioning
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 7a5ad5e..dd39f1d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -104,6 +104,7 @@
 
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
+    private boolean mShownOnSecondaryDisplay = false;
     private boolean mOnlyClock = false;
     private boolean mIsActiveDreamLockscreenHosted = false;
     private FeatureFlags mFeatureFlags;
@@ -185,8 +186,18 @@
     }
 
     /**
-     * Mostly used for alternate displays, limit the information shown
+     * When set, limits the information shown in an external display.
      */
+    public void setShownOnSecondaryDisplay(boolean shownOnSecondaryDisplay) {
+        mShownOnSecondaryDisplay = shownOnSecondaryDisplay;
+    }
+
+    /**
+     * Mostly used for alternate displays, limit the information shown
+     *
+     * @deprecated use {@link KeyguardClockSwitchController#setShownOnSecondaryDisplay}
+     */
+    @Deprecated
     public void setOnlyClock(boolean onlyClock) {
         mOnlyClock = onlyClock;
     }
@@ -221,6 +232,15 @@
         }
     }
 
+    private void hideSliceViewAndNotificationIconContainer() {
+        View ksv = mView.findViewById(R.id.keyguard_slice_view);
+        ksv.setVisibility(View.GONE);
+
+        View nic = mView.findViewById(
+                R.id.left_aligned_notification_icon_container);
+        nic.setVisibility(View.GONE);
+    }
+
     @Override
     protected void onViewAttached() {
         mClockRegistry.registerClockChangeListener(mClockChangedListener);
@@ -234,13 +254,15 @@
         mKeyguardDateWeatherViewInvisibility =
                 mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility);
 
-        if (mOnlyClock) {
-            View ksv = mView.findViewById(R.id.keyguard_slice_view);
-            ksv.setVisibility(View.GONE);
+        if (mShownOnSecondaryDisplay) {
+            mView.setLargeClockOnSecondaryDisplay(true);
+            displayClock(LARGE, /* animate= */ false);
+            hideSliceViewAndNotificationIconContainer();
+            return;
+        }
 
-            View nic = mView.findViewById(
-                    R.id.left_aligned_notification_icon_container);
-            nic.setVisibility(View.GONE);
+        if (mOnlyClock) {
+            hideSliceViewAndNotificationIconContainer();
             return;
         }
         updateAodIcons();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 9f21a31..1c5a575 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -43,16 +43,19 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
 
 @SysUISingleton
 public class KeyguardDisplayManager {
@@ -64,6 +67,9 @@
     private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    private final ConnectedDisplayKeyguardPresentation.Factory
+            mConnectedDisplayKeyguardPresentationFactory;
+    private final FeatureFlags mFeatureFlags;
     private final Context mContext;
 
     private boolean mShowing;
@@ -105,7 +111,10 @@
             @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DeviceStateHelper deviceStateHelper,
-            KeyguardStateController keyguardStateController) {
+            KeyguardStateController keyguardStateController,
+            ConnectedDisplayKeyguardPresentation.Factory
+                    connectedDisplayKeyguardPresentationFactory,
+            FeatureFlags featureFlags) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -115,6 +124,8 @@
         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
         mDeviceStateHelper = deviceStateHelper;
         mKeyguardStateController = keyguardStateController;
+        mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+        mFeatureFlags = featureFlags;
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -185,8 +196,12 @@
         return false;
     }
 
-    KeyguardPresentation createPresentation(Display display) {
-        return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+    Presentation createPresentation(Display display) {
+        if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
+            return mConnectedDisplayKeyguardPresentationFactory.create(display);
+        } else {
+            return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 757022d..c314586 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -187,6 +187,11 @@
         mConfigurationController.removeCallback(mConfigurationListener);
     }
 
+    /** Sets the StatusView as shown on an external display. */
+    public void setDisplayedOnSecondaryDisplay() {
+        mKeyguardClockSwitchController.setShownOnSecondaryDisplay(true);
+    }
+
     /**
      * Called in notificationPanelViewController to avoid leak
      */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index cca71e8..f3900ac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -115,7 +115,7 @@
     // TODO(b/292213543): Tracking Bug
     @JvmField
     val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            unreleasedFlag("notification_group_expansion_change", teamfood = false)
+            unreleasedFlag("notification_group_expansion_change", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -769,6 +769,10 @@
     @JvmField
     val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
 
+    /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
+    @JvmField
+    val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
     val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
new file mode 100644
index 0000000..3f2f67d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ */
+@SysUISingleton
+class KeyEventInteractor
+@Inject
+constructor(
+    private val backActionInteractor: BackActionInteractor,
+    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+            return true
+        }
+
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK -> {
+                if (event.handleAction()) {
+                    backActionInteractor.onBackRequested()
+                }
+                return true
+            }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.interceptMediaKey(event)
+    }
+
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+    }
+
+    companion object {
+        // Most actions shouldn't be handled on the down event and instead handled on subsequent
+        // key events like ACTION_UP.
+        fun KeyEvent.handleAction(): Boolean {
+            return action != KeyEvent.ACTION_DOWN
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
new file mode 100644
index 0000000..635961b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles key events arriving when the keyguard is showing or device is dozing. */
+@SysUISingleton
+class KeyguardKeyEventInteractor
+@Inject
+constructor(
+    private val context: Context,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val shadeController: ShadeController,
+    private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
+    private val backActionInteractor: BackActionInteractor,
+) {
+
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (statusBarStateController.isDozing) {
+            when (event.keyCode) {
+                KeyEvent.KEYCODE_VOLUME_DOWN,
+                KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
+            }
+        }
+
+        if (event.handleAction()) {
+            when (event.keyCode) {
+                KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
+                KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
+            }
+        }
+        return false
+    }
+
+    /**
+     * While IME is active and a BACK event is detected, check with {@link
+     * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
+     * handled before routing to IME, in order to prevent the user from having to hit back twice to
+     * exit bouncer.
+     */
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK ->
+                if (
+                    statusBarStateController.state == StatusBarState.KEYGUARD &&
+                        statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
+                ) {
+                    return backActionInteractor.onBackRequested()
+                }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return statusBarStateController.state == StatusBarState.KEYGUARD &&
+            statusBarKeyguardViewManager.interceptMediaKey(event)
+    }
+
+    private fun dispatchMenuKeyEvent(): Boolean {
+        val shouldUnlockOnMenuPressed =
+            isDeviceInteractive() &&
+                (statusBarStateController.state != StatusBarState.SHADE) &&
+                statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
+        if (shouldUnlockOnMenuPressed) {
+            shadeController.animateCollapseShadeForced()
+            return true
+        }
+        return false
+    }
+
+    private fun dispatchSpaceEvent(): Boolean {
+        if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
+            shadeController.animateCollapseShadeForced()
+            return true
+        }
+        return false
+    }
+
+    private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
+        mediaSessionLegacyHelperWrapper
+            .getHelper(context)
+            .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
+        return true
+    }
+
+    private fun isDeviceInteractive(): Boolean {
+        return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
new file mode 100644
index 0000000..9924369
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSessionLegacyHelper
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Injectable wrapper around `MediaSessionLegacyHelper` functions */
+@SysUISingleton
+class MediaSessionLegacyHelperWrapper @Inject constructor() {
+    fun getHelper(context: Context): MediaSessionLegacyHelper {
+        return MediaSessionLegacyHelper.getHelper(context)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e134f7c..ae0ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,6 +25,7 @@
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
 import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -1714,10 +1715,12 @@
 
     private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
         final InsetsFrameProvider navBarProvider =
-                new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
-                        .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
-                                new InsetsFrameProvider.InsetsSizeOverride(
-                                        TYPE_INPUT_METHOD, null)});
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars());
+        if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+            navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+                    new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+            });
+        }
         if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
             navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 132cd61..df7d88f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -163,6 +163,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -355,6 +356,7 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final KeyguardRootView mKeyguardRootView;
     private final QuickSettingsController mQsController;
+    private final ShadeInteractor mShadeInteractor;
     private final TouchHandler mTouchHandler = new TouchHandler();
 
     private long mDownTime;
@@ -559,7 +561,6 @@
     private float mHintDistance;
     private float mInitialOffsetOnTouch;
     private boolean mCollapsedAndHeadsUpOnDown;
-    private float mExpandedFraction = 0;
     private float mExpansionDragDownAmountPx = 0;
     private boolean mPanelClosedOnDown;
     private boolean mHasLayoutedSinceDown;
@@ -709,10 +710,12 @@
             VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker,
             PowerManager powerManager,
-            AccessibilityManager accessibilityManager, @DisplayId int displayId,
+            AccessibilityManager accessibilityManager,
+            @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
+            ShadeInteractor shadeInteractor,
             ConfigurationController configurationController,
             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -785,6 +788,7 @@
         mView = view;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLockscreenGestureLogger = lockscreenGestureLogger;
+        mShadeInteractor = shadeInteractor;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeLog = shadeLogger;
         mGutsManager = gutsManager;
@@ -2244,7 +2248,7 @@
             if (!isFalseTouch(x, y, interactionType)) {
                 mShadeLog.logFlingExpands(vel, vectorVel, interactionType,
                         this.mFlingAnimationUtils.getMinVelocityPxPerSecond(),
-                        mExpandedFraction > 0.5f, mAllowExpandForSmallExpansion);
+                        getExpandedFraction() > 0.5f, mAllowExpandForSmallExpansion);
                 if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
                     expands = shouldExpandWhenNotFlinging();
                 } else {
@@ -2419,7 +2423,7 @@
     void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScrollLayoutController.updateTopPadding(
                 mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
-                        getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
+                        getKeyguardNotificationStaticPadding(), getExpandedFraction()), animate);
         if (isKeyguardShowing()
                 && mKeyguardBypassController.getBypassEnabled()) {
             // update the position of the header
@@ -2501,10 +2505,10 @@
     private void onHeightUpdated(float expandedHeight) {
         if (expandedHeight <= 0) {
             mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
-                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                    getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
         } else if (isFullyExpanded()) {
             mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
-                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                    getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
         }
         if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
                 || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
@@ -3198,6 +3202,11 @@
         }
     }
 
+    @Override
+    public void performHapticFeedback(int constant) {
+        mVibratorHelper.performHapticFeedback(mView, constant);
+    }
+
     private class ShadeHeadsUpTrackerImpl implements ShadeHeadsUpTracker {
         @Override
         public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
@@ -3425,7 +3434,7 @@
         ipw.print("mHintDistance="); ipw.println(mHintDistance);
         ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
         ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
-        ipw.print("mExpandedFraction="); ipw.println(mExpandedFraction);
+        ipw.print("getExpandedFraction()="); ipw.println(getExpandedFraction());
         ipw.print("mExpansionDragDownAmountPx="); ipw.println(mExpansionDragDownAmountPx);
         ipw.print("mPanelClosedOnDown="); ipw.println(mPanelClosedOnDown);
         ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
@@ -3761,7 +3770,7 @@
 
             // don't fling while in keyguard to avoid jump in shade expand animation;
             // touch has been intercepted already so flinging here is redundant
-            if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) {
+            if (mBarState == KEYGUARD && getExpandedFraction() >= 1.0) {
                 mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard");
             } else {
                 fling(vel, expand, isFalseTouch(x, y, interactionType));
@@ -3857,6 +3866,16 @@
     @VisibleForTesting
     void setExpandedHeight(float height) {
         debugLog("setExpandedHeight(%.1f)", height);
+        int maxPanelTransitionDistance = getMaxPanelTransitionDistance();
+        if (maxPanelTransitionDistance == 0) {
+            setExpandedFracAndHeight(0, height);
+        } else {
+            setExpandedFracAndHeight(height / maxPanelTransitionDistance, height);
+        }
+    }
+
+    private void setExpandedFracAndHeight(float frac, float height) {
+        mShadeInteractor.setExpansion(frac);
         setExpandedHeightInternal(height);
     }
 
@@ -3912,11 +3931,9 @@
                     mHeightAnimator.end();
                 }
             }
-            mExpandedFraction = Math.min(1f,
-                    maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
-            mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
+            mQsController.setShadeExpansion(mExpandedHeight, getExpandedFraction());
             mExpansionDragDownAmountPx = h;
-            mAmbientState.setExpansionFraction(mExpandedFraction);
+            mAmbientState.setExpansionFraction(getExpandedFraction());
             onHeightUpdated(mExpandedHeight);
             updateExpansionAndVisibility();
         });
@@ -3944,8 +3961,7 @@
     /** Sets the expanded height relative to a number from 0 to 1. */
     @VisibleForTesting
     void setExpandedFraction(float frac) {
-        final int maxDist = getMaxPanelTransitionDistance();
-        setExpandedHeight(maxDist * frac);
+        setExpandedFracAndHeight(frac, getMaxPanelTransitionDistance() * frac);
     }
 
     float getExpandedHeight() {
@@ -3953,7 +3969,7 @@
     }
 
     float getExpandedFraction() {
-        return mExpandedFraction;
+        return mShadeInteractor.getExpansion().getValue();
     }
 
     @Override
@@ -3975,7 +3991,7 @@
 
     @Override
     public boolean isFullyCollapsed() {
-        return mExpandedFraction <= 0.0f;
+        return getExpandedFraction() <= 0.0f;
     }
 
     @Override
@@ -4134,7 +4150,7 @@
                                         animator.getAnimatedFraction()));
                         setOverExpansionInternal(expansion, false /* isFromGesture */);
                     }
-                    setExpandedHeightInternal((float) animation.getAnimatedValue());
+                    setExpandedHeight((float) animation.getAnimatedValue());
                 });
         return animator;
     }
@@ -4148,14 +4164,14 @@
     @Override
     public void updateExpansionAndVisibility() {
         mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
 
         updateVisibility();
     }
 
     @Override
     public boolean isExpanded() {
-        return mExpandedFraction > 0f
+        return getExpandedFraction() > 0f
                 || mInstantExpanding
                 || isPanelVisibleBecauseOfHeadsUp()
                 || mTracking
@@ -4913,7 +4929,7 @@
                     mMotionAborted = false;
                     mPanelClosedOnDown = isFullyCollapsed();
                     mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown,
-                            mExpandedFraction);
+                            getExpandedFraction());
                     mCollapsedAndHeadsUpOnDown = false;
                     mHasLayoutedSinceDown = false;
                     mUpdateFlingOnLayout = false;
@@ -5132,7 +5148,7 @@
                     mMinExpandHeight = 0.0f;
                     mPanelClosedOnDown = isFullyCollapsed();
                     mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown,
-                            mExpandedFraction);
+                            getExpandedFraction());
                     mHasLayoutedSinceDown = false;
                     mUpdateFlingOnLayout = false;
                     mMotionAborted = false;
@@ -5191,7 +5207,7 @@
                     if (isFullyCollapsed()) {
                         // If panel is fully collapsed, reset haptic effect before adding movement.
                         mHasVibratedOnOpen = false;
-                        mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
+                        mShadeLog.logHasVibrated(mHasVibratedOnOpen, getExpandedFraction());
                     }
                     addMovement(event);
                     if (!isFullyCollapsed()) {
@@ -5227,7 +5243,7 @@
                         // otherwise {@link NotificationStackScrollLayout}
                         // wrongly enables stack height updates at the start of lockscreen swipe-up
                         mAmbientState.setSwipingUp(h <= 0);
-                        setExpandedHeightInternal(newHeight);
+                        setExpandedHeight(newHeight);
                     }
                     break;
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index bea12de..6564118 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -263,9 +263,11 @@
                     resources.getDimensionPixelSize(
                         R.dimen.shade_header_system_icons_padding_start
                     ),
-                    systemIcons.paddingTop,
+                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top),
                     resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
-                    systemIcons.paddingBottom
+                    resources.getDimensionPixelSize(
+                        R.dimen.shade_header_system_icons_padding_bottom
+                    )
                 )
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d5b5c87..182a676 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -248,6 +248,16 @@
     /** Starts tracking a shade expansion gesture that originated from the status bar. */
     fun startTrackingExpansionFromStatusBar()
 
+    /**
+     * Performs haptic feedback from a view with a haptic feedback constant.
+     *
+     * The implementation of this method should use the [android.view.View.performHapticFeedback]
+     * method with the provided constant.
+     *
+     * @param[constant] One of [android.view.HapticFeedbackConstants]
+     */
+    fun performHapticFeedback(constant: Int)
+
     // ******* End Keyguard Section *********
 
     /** Returns the ShadeHeadsUpTracker. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 287ac52..09b74b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -86,6 +86,8 @@
         return false
     }
     override fun startTrackingExpansionFromStatusBar() {}
+    override fun performHapticFeedback(constant: Int) {}
+
     override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
     override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index ebb9935..76dca4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -34,13 +34,34 @@
     /** ShadeModel information regarding shade expansion events */
     val shadeModel: Flow<ShadeModel>
 
-    /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */
+    /**
+     * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+     * expanded and `0` is fully collapsed. Quick Settings can be expanded without a fully expanded
+     * shade.
+     */
     val qsExpansion: StateFlow<Float>
 
+    /**
+     * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+     * expanded and `0` is fully collapsed.
+     */
+    val expansion: StateFlow<Float>
+
     /** Amount shade has expanded with regard to the UDFPS location */
     val udfpsTransitionToFullShadeProgress: StateFlow<Float>
 
+    /**
+     * Set shade expansion to a value from `0` to `1` representing the amount the shade has expanded
+     * where `1` is fully expanded and `0` is fully collapsed.
+     */
+    fun setExpansion(expansion: Float)
+
+    /**
+     * Set quick settings expansion to a value from `0` to `1` representing the amount quick
+     * settings has expanded where `1` is fully expanded and `0` is fully collapsed.
+     */
     fun setQsExpansion(qsExpansion: Float)
+
     fun setUdfpsTransitionToFullShadeProgress(progress: Float)
 }
 
@@ -78,9 +99,17 @@
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
 
+    private val _expansion = MutableStateFlow(0f)
+    override val expansion: StateFlow<Float> = _expansion.asStateFlow()
+
     private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
         _udfpsTransitionToFullShadeProgress.asStateFlow()
+
+    override fun setExpansion(expansion: Float) {
+        _expansion.value = expansion
+    }
+
     override fun setQsExpansion(qsExpansion: Float) {
         _qsExpansion.value = qsExpansion
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6fde84a..efe3eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -38,18 +39,29 @@
 @Inject
 constructor(
     @Application scope: CoroutineScope,
+    private val shadeRepository: ShadeRepository,
     disableFlagsRepository: DisableFlagsRepository,
     keyguardRepository: KeyguardRepository,
     userSetupRepository: UserSetupRepository,
     deviceProvisionedController: DeviceProvisionedController,
     userInteractor: UserInteractor,
 ) {
+    /**
+     * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
+     * expanded and `0` is fully collapsed.
+     */
+    val expansion = shadeRepository.expansion
+
     /** Emits true if the shade is currently allowed and false otherwise. */
     val isShadeEnabled: StateFlow<Boolean> =
         disableFlagsRepository.disableFlags
             .map { it.isShadeEnabled() }
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
+    fun setExpansion(expansion: Float) {
+        shadeRepository.setExpansion(expansion)
+    }
+
     /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
     val isExpandToQsEnabled: Flow<Boolean> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6431ef9..2bc7b99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
 
@@ -33,12 +34,15 @@
 import android.os.Vibrator;
 import android.util.Log;
 import android.util.Slog;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.LetterboxDetails;
@@ -49,6 +53,7 @@
 import com.android.systemui.camera.CameraIntents;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
@@ -107,6 +112,7 @@
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final QuickSettingsController mQsController;
     private final QSHost mQSHost;
+    private final FeatureFlags mFeatureFlags;
 
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -144,7 +150,8 @@
             Lazy<CameraLauncher> cameraLauncherLazy,
             UserTracker userTracker,
             QSHost qsHost,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter,
+            FeatureFlags featureFlags) {
         mCentralSurfaces = centralSurfaces;
         mQsController = quickSettingsController;
         mContext = context;
@@ -171,6 +178,7 @@
         mCameraLauncherLazy = cameraLauncherLazy;
         mUserTracker = userTracker;
         mQSHost = qsHost;
+        mFeatureFlags = featureFlags;
 
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -314,7 +322,7 @@
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
             if (mShadeViewController.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
-                    mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+                    vibrateOnNavigationKeyDown();
                 }
                 mShadeViewController.expand(true /* animate */);
                 mNotificationStackScrollLayoutController.setWillExpand(true);
@@ -587,4 +595,15 @@
         }
         return VibrationEffect.createWaveform(timings, /* repeat= */ -1);
     }
+
+    @VisibleForTesting
+    void vibrateOnNavigationKeyDown() {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mShadeViewController.performHapticFeedback(
+                    HapticFeedbackConstants.GESTURE_START
+            );
+        } else {
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index 4a31b86..eaae0f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.Dependency
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.time.SystemClock
 import java.text.FieldPosition
@@ -83,6 +84,7 @@
     private val systemClock: SystemClock,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val shadeExpansionStateManager: ShadeExpansionStateManager,
+    private val shadeLogger: ShadeLogger,
     private val timeTickHandler: Handler,
     view: VariableDateView
 ) : ViewController<VariableDateView>(view) {
@@ -111,24 +113,29 @@
 
     private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            if (
+                    Intent.ACTION_LOCALE_CHANGED == action ||
+                    Intent.ACTION_TIMEZONE_CHANGED == action
+            ) {
+                // need to get a fresh date format
+                dateFormat = null
+                shadeLogger.d("VariableDateViewController received intent to refresh date format")
+            }
+
+            val handler = mView.handler
+
             // If the handler is null, it means we received a broadcast while the view has not
             // finished being attached or in the process of being detached.
             // In that case, do not post anything.
-            val handler = mView.handler ?: return
-            val action = intent.action
-            if (
+            if (handler == null) {
+                shadeLogger.d("VariableDateViewController received intent but handler was null")
+            } else if (
                     Intent.ACTION_TIME_TICK == action ||
                     Intent.ACTION_TIME_CHANGED == action ||
                     Intent.ACTION_TIMEZONE_CHANGED == action ||
                     Intent.ACTION_LOCALE_CHANGED == action
             ) {
-                if (
-                        Intent.ACTION_LOCALE_CHANGED == action ||
-                        Intent.ACTION_TIMEZONE_CHANGED == action
-                ) {
-                    // need to get a fresh date format
-                    handler.post { dateFormat = null }
-                }
                 handler.post(::updateClock)
             }
         }
@@ -231,6 +238,7 @@
         private val systemClock: SystemClock,
         private val broadcastDispatcher: BroadcastDispatcher,
         private val shadeExpansionStateManager: ShadeExpansionStateManager,
+        private val shadeLogger: ShadeLogger,
         @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
     ) {
         fun create(view: VariableDateView): VariableDateViewController {
@@ -238,6 +246,7 @@
                     systemClock,
                     broadcastDispatcher,
                     shadeExpansionStateManager,
+                    shadeLogger,
                     handler,
                     view
             )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index b349696..438f0f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
+import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -37,6 +39,7 @@
 
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -59,8 +62,13 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
+    private ConnectedDisplayKeyguardPresentation.Factory
+            mConnectedDisplayKeyguardPresentationFactory;
+    @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
     @Mock
+    private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
+    @Mock
     private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
     @Mock
     private KeyguardStateController mKeyguardStateController;
@@ -69,7 +77,7 @@
     private Executor mBackgroundExecutor = Runnable::run;
     private KeyguardDisplayManager mManager;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
+    private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
     // The default and secondary displays are both in the default group
     private Display mDefaultDisplay;
     private Display mSecondaryDisplay;
@@ -80,10 +88,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
         mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
                 mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
-                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
+                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
+                mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+        doReturn(mConnectedDisplayKeyguardPresentation).when(
+                mConnectedDisplayKeyguardPresentationFactory).create(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
                 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -138,4 +150,15 @@
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
         verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
     }
+
+    @Test
+    public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+        when(mManager.createPresentation(any())).thenCallRealMethod();
+        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+        mManager.show();
+
+        verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
new file mode 100644
index 0000000..632d149
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyEventInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: KeyEventInteractor
+
+    @Before
+    fun setup() {
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            KeyEventInteractor(
+                backActionInteractor,
+                keyguardKeyEventInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+            .thenReturn(false)
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+            .thenReturn(false)
+
+        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+        // THEN back event isn't handled on ACTION_DOWN
+        verify(backActionInteractor, never()).onBackRequested()
+
+        // WHEN back key event ACTION_UP
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+        // THEN back event is handled on ACTION_UP
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
new file mode 100644
index 0000000..a3f7fc5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.media.session.MediaSessionLegacyHelper
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardKeyEventInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private val actionDownVolumeDownKeyEvent =
+        KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
+    private val actionDownVolumeUpKeyEvent =
+        KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+    private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+    private val awakeWakefulnessMode =
+        WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+    private val asleepWakefulnessMode =
+        WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper
+    @Mock private lateinit var mediaSessionLegacyHelper: MediaSessionLegacyHelper
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: KeyguardKeyEventInteractor
+
+    @Before
+    fun setup() {
+        whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
+            .thenReturn(mediaSessionLegacyHelper)
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            KeyguardKeyEventInteractor(
+                context,
+                statusBarStateController,
+                keyguardInteractorWithDependencies.keyguardInteractor,
+                statusBarKeyguardViewManager,
+                shadeController,
+                mediaSessionLegacyHelperWrapper,
+                backActionInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_volumeKey_dozing_handlesEvents() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isTrue()
+        verify(mediaSessionLegacyHelper)
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeDownKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue()
+        verify(mediaSessionLegacyHelper)
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeUpKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_volumeKey_notDozing_doesNotHandleEvents() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isFalse()
+        verify(mediaSessionLegacyHelper, never())
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeDownKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse()
+        verify(mediaSessionLegacyHelper, never())
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeUpKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_keyguard_onBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+
+        whenever(backActionInteractor.onBackRequested()).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor).onBackRequested()
+        clearInvocations(backActionInteractor)
+
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isTrue()
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_keyguard_SBKVMdoesNotHandle_neverOnBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(false)
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor, never()).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_shade_neverOnBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor, never()).onBackRequested()
+    }
+
+    @Test
+    fun interceptMediaKey_keyguard_SBKVMdoesNotHandle_doesNotHandleMediaKey() {
+        val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+        verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+    }
+
+    @Test
+    fun interceptMediaKey_keyguard_handleMediaKey() {
+        val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+        verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+    }
+
+    @Test
+    fun interceptMediaKey_shade_doesNotHandleMediaKey() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+        assertThat(
+                underTest.interceptMediaKey(
+                    KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+                )
+            )
+            .isFalse()
+        verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 981e44b..5cad9f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -73,6 +73,7 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -119,6 +120,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -134,6 +136,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -164,14 +167,17 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.UserInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
@@ -358,6 +364,16 @@
         mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
         mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
         mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+        mShadeRepository = new FakeShadeRepository();
+        mShadeInteractor = new ShadeInteractor(
+                TestScopeProvider.getTestScope(),
+                mShadeRepository,
+                new FakeDisableFlagsRepository(),
+                new FakeKeyguardRepository(),
+                new FakeUserSetupRepository(),
+                mock(DeviceProvisionedController.class),
+                mock(UserInteractor.class)
+        );
 
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
@@ -584,19 +600,33 @@
                 mMainHandler,
                 mLayoutInflater,
                 mFeatureFlags,
-                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
-                mFalsingManager, new FalsingCollectorFake(),
+                coordinator,
+                expansionHandler,
+                mDynamicPrivacyController,
+                mKeyguardBypassController,
+                mFalsingManager,
+                new FalsingCollectorFake(),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarWindowStateController,
                 mNotificationShadeWindowController,
-                mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
-                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+                mDozeLog,
+                mDozeParameters,
+                mCommandQueue,
+                mVibratorHelper,
+                mLatencyTracker,
+                mPowerManager,
+                mAccessibilityManager,
+                0,
+                mUpdateMonitor,
                 mMetricsLogger,
                 mShadeLog,
+                mShadeInteractor,
                 mConfigurationController,
-                () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
-                mConversationNotificationManager, mMediaHierarchyManager,
+                () -> flingAnimationUtilsBuilder,
+                mStatusBarTouchableRegionManager,
+                mConversationNotificationManager,
+                mMediaHierarchyManager,
                 mStatusBarKeyguardViewManager,
                 mGutsManager,
                 mNotificationsQSContainerController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index a2c2912..35a54d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -170,6 +171,7 @@
         mShadeInteractor =
                 new ShadeInteractor(
                         mTestScope.getBackgroundScope(),
+                        new FakeShadeRepository(),
                         mDisableFlagsRepository,
                         mKeyguardRepository,
                         new FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index cdcd1a2..9e6c12f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -67,6 +67,7 @@
     private val featureFlags = FakeFeatureFlags()
     private val userSetupRepository = FakeUserSetupRepository()
     private val userRepository = FakeUserRepository()
+    private val shadeRepository = FakeShadeRepository()
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val keyguardRepository = FakeKeyguardRepository()
 
@@ -138,6 +139,7 @@
         underTest =
             ShadeInteractor(
                 testScope.backgroundScope,
+                shadeRepository,
                 disableFlagsRepository,
                 keyguardRepository,
                 userSetupRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 4a2518a..ec4367c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -102,8 +102,10 @@
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val keyguardRepository = FakeKeyguardRepository()
+    private val shadeRepository = FakeShadeRepository()
     private val shadeInteractor = ShadeInteractor(
         testScope.backgroundScope,
+        shadeRepository,
         disableFlagsRepository,
         keyguardRepository,
         userSetupRepository = FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 8545b89..3ad3c15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -29,8 +31,10 @@
 import android.app.StatusBarManager;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
+import android.view.HapticFeedbackConstants;
 import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
@@ -42,6 +46,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
@@ -98,6 +103,7 @@
     @Mock private UserTracker mUserTracker;
     @Mock private QSHost mQSHost;
     @Mock private ActivityStarter mActivityStarter;
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -134,7 +140,8 @@
                 mCameraLauncherLazy,
                 mUserTracker,
                 mQSHost,
-                mActivityStarter);
+                mActivityStarter,
+                mFeatureFlags);
 
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
@@ -241,4 +248,24 @@
 
         verifyZeroInteractions(mSystemBarAttributesListener);
     }
+
+    @Test
+    public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+        mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+        verify(mVibratorHelper).vibrate(VibrationEffect.EFFECT_TICK);
+    }
+
+    @Test
+    public void vibrateOnNavigationKeyDown_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+        mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+        verify(mShadeViewController).performHapticFeedback(
+                HapticFeedbackConstants.GESTURE_START
+        );
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index b698e70..b78e839 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -105,6 +106,7 @@
                 systemClock,
                 broadcastDispatcher,
                 shadeExpansionStateManager,
+                mock(),
                 testableHandler,
                 view
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 492e542..35a3fd0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,6 +30,9 @@
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion = _qsExpansion
 
+    private val _expansion = MutableStateFlow(0f)
+    override val expansion = _expansion
+
     private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
 
@@ -41,6 +44,10 @@
         _qsExpansion.value = qsExpansion
     }
 
+    override fun setExpansion(expansion: Float) {
+        _expansion.value = expansion
+    }
+
     override fun setUdfpsTransitionToFullShadeProgress(progress: Float) {
         _udfpsTransitionToFullShadeProgress.value = progress
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 0380756..97e5c6f 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -18,10 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -53,9 +57,21 @@
 
     @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
 
-    @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister;
+    // TODO(b/295582896): Find a way to make this NonNull
+    @Nullable private AuthenticationStatsPersister mAuthenticationStatsPersister;
     @NonNull private BiometricNotification mBiometricNotification;
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId != UserHandle.USER_NULL
+                    && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+                onUserRemoved(userId);
+            }
+        }
+    };
+
     public AuthenticationStatsCollector(@NonNull Context context, int modality,
             @NonNull BiometricNotification biometricNotification) {
         mContext = context;
@@ -64,6 +80,8 @@
         mUserAuthenticationStatsMap = new HashMap<>();
         mModality = modality;
         mBiometricNotification = biometricNotification;
+
+        context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
     }
 
     private void initializeUserAuthenticationStatsMap() {
@@ -75,8 +93,9 @@
 
     /** Update total authentication and rejected attempts. */
     public void authenticate(int userId, boolean authenticated) {
-        // Initialize mUserAuthenticationStatsMap when authenticate to ensure SharedPreferences
-        // are ready for application use and avoid ramdump issue.
+        // SharedPreference is not ready when starting system server, initialize
+        // mUserAuthenticationStatsMap in authentication to ensure SharedPreference
+        // is ready for application use.
         if (mUserAuthenticationStatsMap.isEmpty()) {
             initializeUserAuthenticationStatsMap();
         }
@@ -146,6 +165,14 @@
         }
     }
 
+    private void onUserRemoved(final int userId) {
+        if (mAuthenticationStatsPersister == null) {
+            initializeUserAuthenticationStatsMap();
+        }
+        mUserAuthenticationStatsMap.remove(userId);
+        mAuthenticationStatsPersister.removeFrrStats(userId);
+    }
+
     /**
      * Only being used in tests. Callers should not make any changes to the returned
      * authentication stats.
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 96150a6..21e93a8 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -94,12 +94,35 @@
     }
 
     /**
+     * Remove frr data for a specific user.
+     */
+    public void removeFrrStats(int userId) {
+        try {
+            // Copy into a new HashSet to allow modification.
+            Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+            // Remove the old authentication stat for the user if it exists.
+            for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+                String frrStats = iterator.next();
+                JSONObject frrStatJson = new JSONObject(frrStats);
+                if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+                    iterator.remove();
+                    break;
+                }
+            }
+
+            mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+        } catch (JSONException ignored) {
+        }
+    }
+
+    /**
      * Persist frr data for a specific user.
      */
     public void persistFrrStats(int userId, int totalAttempts, int rejectedAttempts,
             int enrollmentNotifications, int modality) {
         try {
-            // Copy into a new HashSet to avoid iterator exception.
+            // Copy into a new HashSet to allow modification.
             Set<String> frrStatsSet = new HashSet<>(readFrrStats());
 
             // Remove the old authentication stat for the user if it exists.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 230ba63..2ff695d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -87,11 +87,11 @@
     public static void showFaceEnrollNotification(@NonNull Context context) {
 
         final String name =
-                context.getString(R.string.face_recalibrate_notification_name);
+                context.getString(R.string.device_unlock_notification_name);
         final String title =
-                context.getString(R.string.fingerprint_setup_notification_title);
+                context.getString(R.string.alternative_unlock_setup_notification_title);
         final String content =
-                context.getString(R.string.face_setup_notification_title);
+                context.getString(R.string.alternative_face_setup_notification_content);
 
         final Intent intent = new Intent(FACE_ENROLL_ACTION);
         intent.setPackage(SETTINGS_PACKAGE);
@@ -112,11 +112,11 @@
     public static void showFingerprintEnrollNotification(@NonNull Context context) {
 
         final String name =
-                context.getString(R.string.fingerprint_recalibrate_notification_name);
+                context.getString(R.string.device_unlock_notification_name);
         final String title =
-                context.getString(R.string.fingerprint_setup_notification_title);
+                context.getString(R.string.alternative_unlock_setup_notification_title);
         final String content =
-                context.getString(R.string.fingerprint_setup_notification_content);
+                context.getString(R.string.alternative_fp_setup_notification_content);
 
         final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
         intent.setPackage(SETTINGS_PACKAGE);
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 4ad26c4..7ea576d 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -63,10 +63,15 @@
     // high errors. This default is introduced to provide a fixed display color
     // temperature when sensor readings become unreliable.
     private final float mLowLightAmbientColorTemperature;
+    // As above, but used when in strong mode (idle screen brightness mode).
+    private final float mLowLightAmbientColorTemperatureStrong;
+
     // In high brightness conditions certain color temperatures can cause peak display
     // brightness to drop. This fixed color temperature can be used to compensate for
     // this effect.
     private final float mHighLightAmbientColorTemperature;
+    // As above, but used when in strong mode (idle screen brightness mode).
+    private final float mHighLightAmbientColorTemperatureStrong;
 
     private final boolean mLightModeAllowed;
 
@@ -97,9 +102,11 @@
     // ambient color temperature to the defaults. A piecewise linear relationship
     // between low light brightness and low light bias.
     private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSpline;
+    private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSplineStrong;
 
     // A piecewise linear relationship between high light brightness and high light bias.
     private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline;
+    private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSplineStrong;
 
     private float mLatestAmbientColorTemperature;
     private float mLatestAmbientBrightness;
@@ -134,17 +141,29 @@
      * @param lowLightAmbientBrightnesses
      *      The ambient brightness used to map the ambient brightnesses to the biases used to
      *      interpolate to lowLightAmbientColorTemperature.
+     * @param lowLightAmbientBrightnessesStrong
+     *      The ambient brightness used to map the ambient brightnesses to the biases used to
+     *      interpolate to lowLightAmbientColorTemperature.
      * @param lowLightAmbientBiases
      *      The biases used to map the ambient brightnesses to the biases used to interpolate to
      *      lowLightAmbientColorTemperature.
+     * @param lowLightAmbientBiasesStrong
+     *      The biases used to map the ambient brightnesses to the biases used to interpolate to
+     *      lowLightAmbientColorTemperature.
      * @param lowLightAmbientColorTemperature
      *      The ambient color temperature to which we interpolate to based on the low light curve.
      * @param highLightAmbientBrightnesses
      *      The ambient brightness used to map the ambient brightnesses to the biases used to
      *      interpolate to highLightAmbientColorTemperature.
+     * @param highLightAmbientBrightnessesStrong
+     *      The ambient brightness used to map the ambient brightnesses to the biases used to
+     *      interpolate to highLightAmbientColorTemperature.
      * @param highLightAmbientBiases
      *      The biases used to map the ambient brightnesses to the biases used to interpolate to
      *      highLightAmbientColorTemperature.
+     * @param highLightAmbientBiasesStrong
+     *      The biases used to map the ambient brightnesses to the biases used to interpolate to
+     *      highLightAmbientColorTemperature.
      * @param highLightAmbientColorTemperature
      *      The ambient color temperature to which we interpolate to based on the high light curve.
      * @param ambientColorTemperatures
@@ -170,11 +189,17 @@
             @NonNull AmbientFilter colorTemperatureFilter,
             @NonNull DisplayWhiteBalanceThrottler throttler,
             float[] lowLightAmbientBrightnesses,
+            float[] lowLightAmbientBrightnessesStrong,
             float[] lowLightAmbientBiases,
+            float[] lowLightAmbientBiasesStrong,
             float lowLightAmbientColorTemperature,
+            float lowLightAmbientColorTemperatureStrong,
             float[] highLightAmbientBrightnesses,
+            float[] highLightAmbientBrightnessesStrong,
             float[] highLightAmbientBiases,
+            float[] highLightAmbientBiasesStrong,
             float highLightAmbientColorTemperature,
+            float highLightAmbientColorTemperatureStrong,
             float[] ambientColorTemperatures,
             float[] displayColorTemperatures,
             float[] strongAmbientColorTemperatures,
@@ -188,7 +213,9 @@
         mColorTemperatureFilter = colorTemperatureFilter;
         mThrottler = throttler;
         mLowLightAmbientColorTemperature = lowLightAmbientColorTemperature;
+        mLowLightAmbientColorTemperatureStrong = lowLightAmbientColorTemperatureStrong;
         mHighLightAmbientColorTemperature = highLightAmbientColorTemperature;
+        mHighLightAmbientColorTemperatureStrong = highLightAmbientColorTemperatureStrong;
         mAmbientColorTemperature = -1.0f;
         mPendingAmbientColorTemperature = -1.0f;
         mLastAmbientColorTemperature = -1.0f;
@@ -214,6 +241,23 @@
         }
 
         try {
+            mLowLightAmbientBrightnessToBiasSplineStrong = new Spline.LinearSpline(
+                    lowLightAmbientBrightnessesStrong, lowLightAmbientBiasesStrong);
+        } catch (Exception e) {
+            Slog.e(TAG, "failed to create strong low light ambient brightness to bias spline.", e);
+            mLowLightAmbientBrightnessToBiasSplineStrong = null;
+        }
+        if (mLowLightAmbientBrightnessToBiasSplineStrong != null) {
+            if (mLowLightAmbientBrightnessToBiasSplineStrong.interpolate(0.0f) != 0.0f
+                    || mLowLightAmbientBrightnessToBiasSplineStrong.interpolate(
+                    Float.POSITIVE_INFINITY) != 1.0f) {
+                Slog.d(TAG, "invalid strong low light ambient brightness to bias spline, "
+                        + "bias must begin at 0.0 and end at 1.0.");
+                mLowLightAmbientBrightnessToBiasSplineStrong = null;
+            }
+        }
+
+        try {
             mHighLightAmbientBrightnessToBiasSpline = new Spline.LinearSpline(
                     highLightAmbientBrightnesses, highLightAmbientBiases);
         } catch (Exception e) {
@@ -230,6 +274,23 @@
             }
         }
 
+        try {
+            mHighLightAmbientBrightnessToBiasSplineStrong = new Spline.LinearSpline(
+                    highLightAmbientBrightnessesStrong, highLightAmbientBiasesStrong);
+        } catch (Exception e) {
+            Slog.e(TAG, "failed to create strong high light ambient brightness to bias spline.", e);
+            mHighLightAmbientBrightnessToBiasSplineStrong = null;
+        }
+        if (mHighLightAmbientBrightnessToBiasSplineStrong != null) {
+            if (mHighLightAmbientBrightnessToBiasSplineStrong.interpolate(0.0f) != 0.0f
+                    || mHighLightAmbientBrightnessToBiasSplineStrong.interpolate(
+                    Float.POSITIVE_INFINITY) != 1.0f) {
+                Slog.d(TAG, "invalid strong high light ambient brightness to bias spline, "
+                        + "bias must begin at 0.0 and end at 1.0.");
+                mHighLightAmbientBrightnessToBiasSplineStrong = null;
+            }
+        }
+
         if (mLowLightAmbientBrightnessToBiasSpline != null &&
                 mHighLightAmbientBrightnessToBiasSpline != null) {
             if (lowLightAmbientBrightnesses[lowLightAmbientBrightnesses.length - 1] >
@@ -241,6 +302,18 @@
             }
         }
 
+        if (mLowLightAmbientBrightnessToBiasSplineStrong != null
+                && mHighLightAmbientBrightnessToBiasSplineStrong != null) {
+            if (lowLightAmbientBrightnessesStrong[lowLightAmbientBrightnessesStrong.length - 1]
+                    > highLightAmbientBrightnessesStrong[0]) {
+                Slog.d(TAG,
+                        "invalid strong low light and high light ambient brightness to bias "
+                                + "spline combination, defined domains must not intersect.");
+                mLowLightAmbientBrightnessToBiasSplineStrong = null;
+                mHighLightAmbientBrightnessToBiasSplineStrong = null;
+            }
+        }
+
         try {
             mAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
                     ambientColorTemperatures, displayColorTemperatures);
@@ -365,7 +438,11 @@
         mColorTemperatureFilter.dump(writer);
         mThrottler.dump(writer);
         writer.println("  mLowLightAmbientColorTemperature=" + mLowLightAmbientColorTemperature);
+        writer.println("  mLowLightAmbientColorTemperatureStrong="
+                + mLowLightAmbientColorTemperatureStrong);
         writer.println("  mHighLightAmbientColorTemperature=" + mHighLightAmbientColorTemperature);
+        writer.println("  mHighLightAmbientColorTemperatureStrong="
+                + mHighLightAmbientColorTemperatureStrong);
         writer.println("  mAmbientColorTemperature=" + mAmbientColorTemperature);
         writer.println("  mPendingAmbientColorTemperature=" + mPendingAmbientColorTemperature);
         writer.println("  mLastAmbientColorTemperature=" + mLastAmbientColorTemperature);
@@ -377,8 +454,12 @@
                 + mStrongAmbientToDisplayColorTemperatureSpline);
         writer.println("  mLowLightAmbientBrightnessToBiasSpline="
                 + mLowLightAmbientBrightnessToBiasSpline);
+        writer.println("  mLowLightAmbientBrightnessToBiasSplineStrong="
+                + mLowLightAmbientBrightnessToBiasSplineStrong);
         writer.println("  mHighLightAmbientBrightnessToBiasSpline="
                 + mHighLightAmbientBrightnessToBiasSpline);
+        writer.println("  mHighLightAmbientBrightnessToBiasSplineStrong="
+                + mHighLightAmbientBrightnessToBiasSplineStrong);
     }
 
     @Override // AmbientSensor.AmbientBrightnessSensor.Callbacks
@@ -400,6 +481,17 @@
      */
     public void updateAmbientColorTemperature() {
         final long time = System.currentTimeMillis();
+        final float lowLightAmbientColorTemperature = mStrongModeEnabled
+                ? mLowLightAmbientColorTemperatureStrong : mLowLightAmbientColorTemperature;
+        final float highLightAmbientColorTemperature = mStrongModeEnabled
+                ? mHighLightAmbientColorTemperatureStrong : mHighLightAmbientColorTemperature;
+        final Spline.LinearSpline lowLightAmbientBrightnessToBiasSpline = mStrongModeEnabled
+                ? mLowLightAmbientBrightnessToBiasSplineStrong
+                : mLowLightAmbientBrightnessToBiasSpline;
+        final Spline.LinearSpline highLightAmbientBrightnessToBiasSpline = mStrongModeEnabled
+                ? mHighLightAmbientBrightnessToBiasSplineStrong
+                : mHighLightAmbientBrightnessToBiasSpline;
+
         float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
         mLatestAmbientColorTemperature = ambientColorTemperature;
 
@@ -423,19 +515,19 @@
         mLatestAmbientBrightness = ambientBrightness;
 
         if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
-                && mLowLightAmbientBrightnessToBiasSpline != null) {
-            float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
+                && lowLightAmbientBrightnessToBiasSpline != null) {
+            float bias = lowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
             ambientColorTemperature =
                     bias * ambientColorTemperature + (1.0f - bias)
-                    * mLowLightAmbientColorTemperature;
+                    * lowLightAmbientColorTemperature;
             mLatestLowLightBias = bias;
         }
         if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
-                && mHighLightAmbientBrightnessToBiasSpline != null) {
-            float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
+                && highLightAmbientBrightnessToBiasSpline != null) {
+            float bias = highLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
             ambientColorTemperature =
                     (1.0f - bias) * ambientColorTemperature + bias
-                    * mHighLightAmbientColorTemperature;
+                    * highLightAmbientColorTemperature;
             mLatestHighLightBias = bias;
         }
 
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index 62f813f..39e6b3f 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -70,21 +70,39 @@
         final float[] displayWhiteBalanceLowLightAmbientBrightnesses = getFloatArray(resources,
                 com.android.internal.R.array
                 .config_displayWhiteBalanceLowLightAmbientBrightnesses);
+        final float[] displayWhiteBalanceLowLightAmbientBrightnessesStrong = getFloatArray(
+                resources, com.android.internal.R.array
+                .config_displayWhiteBalanceLowLightAmbientBrightnessesStrong);
         final float[] displayWhiteBalanceLowLightAmbientBiases = getFloatArray(resources,
                 com.android.internal.R.array
                 .config_displayWhiteBalanceLowLightAmbientBiases);
+        final float[] displayWhiteBalanceLowLightAmbientBiasesStrong = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceLowLightAmbientBiasesStrong);
         final float lowLightAmbientColorTemperature = getFloat(resources,
                 com.android.internal.R.dimen
                 .config_displayWhiteBalanceLowLightAmbientColorTemperature);
+        final float lowLightAmbientColorTemperatureStrong = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong);
         final float[] displayWhiteBalanceHighLightAmbientBrightnesses = getFloatArray(resources,
                 com.android.internal.R.array
                 .config_displayWhiteBalanceHighLightAmbientBrightnesses);
+        final float[] displayWhiteBalanceHighLightAmbientBrightnessesStrong = getFloatArray(
+                resources, com.android.internal.R.array
+                .config_displayWhiteBalanceHighLightAmbientBrightnessesStrong);
         final float[] displayWhiteBalanceHighLightAmbientBiases = getFloatArray(resources,
                 com.android.internal.R.array
                 .config_displayWhiteBalanceHighLightAmbientBiases);
+        final float[] displayWhiteBalanceHighLightAmbientBiasesStrong = getFloatArray(resources,
+                com.android.internal.R.array
+                .config_displayWhiteBalanceHighLightAmbientBiasesStrong);
         final float highLightAmbientColorTemperature = getFloat(resources,
                 com.android.internal.R.dimen
                 .config_displayWhiteBalanceHighLightAmbientColorTemperature);
+        final float highLightAmbientColorTemperatureStrong = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong);
         final float[] ambientColorTemperatures = getFloatArray(resources,
                 com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
         final float[] displayColorTemperatures = getFloatArray(resources,
@@ -100,9 +118,15 @@
         final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
                 brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
                 throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
-                displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
+                displayWhiteBalanceLowLightAmbientBrightnessesStrong,
+                displayWhiteBalanceLowLightAmbientBiases,
+                displayWhiteBalanceLowLightAmbientBiasesStrong, lowLightAmbientColorTemperature,
+                lowLightAmbientColorTemperatureStrong,
                 displayWhiteBalanceHighLightAmbientBrightnesses,
-                displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
+                displayWhiteBalanceHighLightAmbientBrightnessesStrong,
+                displayWhiteBalanceHighLightAmbientBiases,
+                displayWhiteBalanceHighLightAmbientBiasesStrong, highLightAmbientColorTemperature,
+                highLightAmbientColorTemperatureStrong,
                 ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
                 strongDisplayColorTemperatures, lightModeAllowed);
         brightnessSensor.setCallbacks(controller);
diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java
index 02e0eb0..39519ef 100644
--- a/services/core/java/com/android/server/input/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/FocusEventDebugView.java
@@ -28,7 +28,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Typeface;
-import android.util.Log;
+import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.TypedValue;
@@ -38,22 +38,26 @@
 import android.view.MotionEvent;
 import android.view.RoundedCorner;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  *  Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
  *  the screen.
  */
-class FocusEventDebugView extends LinearLayout {
+class FocusEventDebugView extends RelativeLayout {
 
     private static final String TAG = FocusEventDebugView.class.getSimpleName();
 
@@ -80,18 +84,24 @@
     private PressedKeyContainer mPressedKeyContainer;
     @Nullable
     private PressedKeyContainer mPressedModifierContainer;
+    private final Supplier<RotaryInputValueView> mRotaryInputValueViewFactory;
+    @Nullable
+    private RotaryInputValueView mRotaryInputValueView;
 
-    FocusEventDebugView(Context c, InputManagerService service) {
+    @VisibleForTesting
+    FocusEventDebugView(Context c, InputManagerService service,
+            Supplier<RotaryInputValueView> rotaryInputValueViewFactory) {
         super(c);
         setFocusableInTouchMode(true);
 
         mService = service;
+        mRotaryInputValueViewFactory = rotaryInputValueViewFactory;
         final var dm = mContext.getResources().getDisplayMetrics();
         mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm);
+    }
 
-        setOrientation(HORIZONTAL);
-        setLayoutDirection(LAYOUT_DIRECTION_RTL);
-        setGravity(Gravity.START | Gravity.BOTTOM);
+    FocusEventDebugView(Context c, InputManagerService service) {
+        this(c, service, () -> new RotaryInputValueView(c));
     }
 
     @Override
@@ -100,13 +110,13 @@
 
         final RoundedCorner bottomLeft =
                 insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        if (bottomLeft != null) {
+        if (bottomLeft != null && !insets.isRound()) {
             paddingBottom = bottomLeft.getRadius();
         }
 
         final RoundedCorner bottomRight =
                 insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
-        if (bottomRight != null) {
+        if (bottomRight != null && !insets.isRound()) {
             paddingBottom = Math.max(paddingBottom, bottomRight.getRadius());
         }
 
@@ -151,7 +161,7 @@
         }
 
         mPressedKeyContainer = new PressedKeyContainer(mContext);
-        mPressedKeyContainer.setOrientation(HORIZONTAL);
+        mPressedKeyContainer.setOrientation(LinearLayout.HORIZONTAL);
         mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
         mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
         final var scroller = new HorizontalScrollView(mContext);
@@ -160,15 +170,23 @@
         scroller.addOnLayoutChangeListener(
                 (view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
         scroller.setHorizontalFadingEdgeEnabled(true);
-        addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));
+        LayoutParams scrollerLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        scrollerLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+        scrollerLayoutParams.addRule(ALIGN_PARENT_RIGHT);
+        addView(scroller, scrollerLayoutParams);
 
         mPressedModifierContainer = new PressedKeyContainer(mContext);
-        mPressedModifierContainer.setOrientation(VERTICAL);
+        mPressedModifierContainer.setOrientation(LinearLayout.VERTICAL);
         mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
-        addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        LayoutParams modifierLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        modifierLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+        modifierLayoutParams.addRule(ALIGN_PARENT_LEFT);
+        modifierLayoutParams.addRule(LEFT_OF, scroller.getId());
+        addView(mPressedModifierContainer, modifierLayoutParams);
     }
 
-    private void handleUpdateShowRotaryInput(boolean enabled) {
+    @VisibleForTesting
+    void handleUpdateShowRotaryInput(boolean enabled) {
         if (enabled == showRotaryInput()) {
             return;
         }
@@ -176,10 +194,18 @@
         if (!enabled) {
             mFocusEventDebugGlobalMonitor.dispose();
             mFocusEventDebugGlobalMonitor = null;
+            removeView(mRotaryInputValueView);
+            mRotaryInputValueView = null;
             return;
         }
 
         mFocusEventDebugGlobalMonitor = new FocusEventDebugGlobalMonitor(this, mService);
+
+        mRotaryInputValueView = mRotaryInputValueViewFactory.get();
+        LayoutParams valueLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+        valueLayoutParams.addRule(CENTER_HORIZONTAL);
+        valueLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+        addView(mRotaryInputValueView, valueLayoutParams);
     }
 
     /** Report a key event to the debug view. */
@@ -242,14 +268,14 @@
         keyEvent.recycle();
     }
 
-    private void handleRotaryInput(MotionEvent motionEvent) {
+    @VisibleForTesting
+    void handleRotaryInput(MotionEvent motionEvent) {
         if (!showRotaryInput()) {
             return;
         }
 
         float scrollAxisValue = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
-        // TODO(b/286086154): replace log with visualization.
-        Log.d(TAG, "ROTARY INPUT: " + String.valueOf(scrollAxisValue));
+        mRotaryInputValueView.updateValue(scrollAxisValue);
 
         motionEvent.recycle();
     }
@@ -308,7 +334,14 @@
 
     /** Determine whether to show rotary input by checking one of the rotary-related objects. */
     private boolean showRotaryInput() {
-        return mFocusEventDebugGlobalMonitor != null;
+        return mRotaryInputValueView != null;
+    }
+
+    /**
+     * Converts a dimension in scaled pixel units to integer display pixels.
+     */
+    private static int applyDimensionSp(int dimensionSp, DisplayMetrics dm) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, dimensionSp, dm);
     }
 
     private static class PressedKeyView extends TextView {
@@ -419,4 +452,66 @@
             invalidate();
         }
     }
+
+    /** Draws the most recent rotary input value and indicates whether the source is active. */
+    @VisibleForTesting
+    static class RotaryInputValueView extends TextView {
+
+        private static final int INACTIVE_TEXT_COLOR = 0xffff00ff;
+        private static final int ACTIVE_TEXT_COLOR = 0xff420f28;
+        private static final int TEXT_SIZE_SP = 8;
+        private static final int SIDE_PADDING_SP = 4;
+        /** Determines how long the active status lasts. */
+        private static final int ACTIVE_STATUS_DURATION = 250 /* milliseconds */;
+        private static final ColorFilter ACTIVE_BACKGROUND_FILTER =
+                new ColorMatrixColorFilter(new float[]{
+                        0, 0, 0, 0, 255, // red
+                        0, 0, 0, 0,   0, // green
+                        0, 0, 0, 0, 255, // blue
+                        0, 0, 0, 0, 200  // alpha
+                });
+
+        private final Runnable mUpdateActivityStatusCallback = () -> updateActivityStatus(false);
+        private final float mScaledVerticalScrollFactor;
+
+        @VisibleForTesting
+        RotaryInputValueView(Context c) {
+            super(c);
+
+            DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+            mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();
+
+            setText(getFormattedValue(0));
+            setTextColor(INACTIVE_TEXT_COLOR);
+            setTextSize(applyDimensionSp(TEXT_SIZE_SP, dm));
+            setPaddingRelative(applyDimensionSp(SIDE_PADDING_SP, dm), 0,
+                    applyDimensionSp(SIDE_PADDING_SP, dm), 0);
+            setTypeface(null, Typeface.BOLD);
+            setBackgroundResource(R.drawable.focus_event_rotary_input_background);
+        }
+
+        void updateValue(float value) {
+            removeCallbacks(mUpdateActivityStatusCallback);
+
+            setText(getFormattedValue(value * mScaledVerticalScrollFactor));
+
+            updateActivityStatus(true);
+            postDelayed(mUpdateActivityStatusCallback, ACTIVE_STATUS_DURATION);
+        }
+
+        @VisibleForTesting
+        void updateActivityStatus(boolean active) {
+            if (active) {
+                setTextColor(ACTIVE_TEXT_COLOR);
+                getBackground().setColorFilter(ACTIVE_BACKGROUND_FILTER);
+            } else {
+                setTextColor(INACTIVE_TEXT_COLOR);
+                getBackground().clearColorFilter();
+            }
+        }
+
+        private static String getFormattedValue(float value) {
+            return String.format("%s%.1f", value < 0 ? "-" : "+", Math.abs(value));
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2fc4829..8e7baf2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5959,6 +5959,8 @@
             mVisibilityStateComputer.dump(pw);
             p.println("  mInFullscreenMode=" + mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+            p.println("  ENABLE_HIDE_IME_CAPTION_BAR="
+                    + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
             p.println("  mSettingsObserver=" + mSettingsObserver);
             p.println("  mStylusIds=" + (mStylusIds != null
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b2d3fca..100c638 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,8 +25,6 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -48,6 +46,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.provider.Settings;
@@ -98,8 +97,7 @@
     // the period after which a notification is updated where it can make sound
     private static final int MAX_SOUND_DELAY_MS = 2000;
     private final StatusBarNotification sbn;
-    IActivityManager mAm;
-    UriGrantsManagerInternal mUgmInternal;
+    private final UriGrantsManagerInternal mUgmInternal;
     final int mTargetSdkVersion;
     final int mOriginalFlags;
     private final Context mContext;
@@ -223,7 +221,6 @@
         this.sbn = sbn;
         mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
                 .getPackageTargetSdkVersion(sbn.getPackageName());
-        mAm = ActivityManager.getService();
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mOriginalFlags = sbn.getNotification().flags;
         mRankingTimeMs = calculateRankingTimeMs(0L);
@@ -1387,18 +1384,27 @@
      * Collect all {@link Uri} that should have permission granted to whoever
      * will be rendering it.
      */
-    protected void calculateGrantableUris() {
-        final Notification notification = getNotification();
-        notification.visitUris((uri) -> {
-            visitGrantableUri(uri, false, false);
-        });
+    private void calculateGrantableUris() {
+        Trace.beginSection("NotificationRecord.calculateGrantableUris");
+        try {
+            // We can't grant URI permissions from system.
+            final int sourceUid = getSbn().getUid();
+            if (sourceUid == android.os.Process.SYSTEM_UID) return;
 
-        if (notification.getChannelId() != null) {
-            NotificationChannel channel = getChannel();
-            if (channel != null) {
-                visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
-                        & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+            final Notification notification = getNotification();
+            notification.visitUris((uri) -> {
+                visitGrantableUri(uri, false, false);
+            });
+
+            if (notification.getChannelId() != null) {
+                NotificationChannel channel = getChannel();
+                if (channel != null) {
+                    visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+                            & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+                }
             }
+        } finally {
+            Trace.endSection();
         }
     }
 
@@ -1413,13 +1419,14 @@
     private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
 
-        // We can't grant Uri permissions from system
-        final int sourceUid = getSbn().getUid();
-        if (sourceUid == android.os.Process.SYSTEM_UID) return;
+        if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+            return; // already verified this URI
+        }
 
+        final int sourceUid = getSbn().getUid();
         final long ident = Binder.clearCallingIdentity();
         try {
-            // This will throw SecurityException if caller can't grant
+            // This will throw a SecurityException if the caller can't grant.
             mUgmInternal.checkGrantUriPermission(sourceUid, null,
                     ContentProvider.getUriWithoutUserId(uri),
                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 385dfcb8..f2797eb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4907,6 +4907,11 @@
                                 USER_OPERATION_ERROR_UNKNOWN);
                     }
                 }
+                if (isMainUser && getMainUserIdUnchecked() != UserHandle.USER_NULL) {
+                    throwCheckedUserOperationException(
+                            "Cannot add user with FLAG_MAIN as main user already exists.",
+                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
+                }
                 if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
                     throwCheckedUserOperationException(
                             "Cannot add more users of type " + userType
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3125518..cba215a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -224,6 +224,9 @@
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -2690,6 +2693,11 @@
         if (isTransferringSplashScreen()) {
             return true;
         }
+        // Only do transfer after transaction has done when starting window exist.
+        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
+            mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+            return true;
+        }
         requestCopySplashScreen();
         return isTransferringSplashScreen();
     }
@@ -2850,11 +2858,14 @@
         if (mStartingData == null) {
             return;
         }
-        mStartingData.mWaitForSyncTransactionCommit = false;
-        if (mStartingData.mRemoveAfterTransaction) {
-            mStartingData.mRemoveAfterTransaction = false;
-            removeStartingWindowAnimation(mStartingData.mPrepareRemoveAnimation);
+        final StartingData lastData = mStartingData;
+        lastData.mWaitForSyncTransactionCommit = false;
+        if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+            removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation);
+        } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
+            removeStartingWindow();
         }
+        lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
     }
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -2881,7 +2892,7 @@
         if (mStartingData != null) {
             if (mStartingData.mWaitForSyncTransactionCommit
                     || mTransitionController.inCollectingTransition(startingWindow)) {
-                mStartingData.mRemoveAfterTransaction = true;
+                mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 34806bd..a23547e 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import android.annotation.IntDef;
+
 import com.android.server.wm.StartingSurfaceController.StartingSurface;
 
 /**
@@ -23,6 +25,20 @@
  */
 public abstract class StartingData {
 
+    /** Nothing need to do after transaction */
+    static final int AFTER_TRANSACTION_IDLE = 0;
+    /** Remove the starting window directly after transaction done. */
+    static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
+    /** Do copy splash screen to client after transaction done. */
+    static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+
+    @IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
+            AFTER_TRANSACTION_IDLE,
+            AFTER_TRANSACTION_REMOVE_DIRECTLY,
+            AFTER_TRANSACTION_COPY_TO_CLIENT,
+    })
+    @interface AfterTransaction {}
+
     protected final WindowManagerService mService;
     protected final int mTypeParams;
 
@@ -60,7 +76,7 @@
      * This starting window should be removed after applying the start transaction of transition,
      * which ensures the app window has shown.
      */
-    boolean mRemoveAfterTransaction;
+    @AfterTransaction int mRemoveAfterTransaction;
 
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index f975b6f..183a84d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -64,7 +64,9 @@
     private static final int AMBIENT_COLOR_TYPE = 20705;
     private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
     private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
+    private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG = 5555.5f;
     private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
+    private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG = 3333.3f;
 
     private Handler mHandler = new Handler(Looper.getMainLooper());
     private Sensor mLightSensor;
@@ -78,6 +80,10 @@
     @Mock private TypedArray mBiases;
     @Mock private TypedArray mHighLightBrightnesses;
     @Mock private TypedArray mHighLightBiases;
+    @Mock private TypedArray mBrightnessesStrong;
+    @Mock private TypedArray mBiasesStrong;
+    @Mock private TypedArray mHighLightBrightnessesStrong;
+    @Mock private TypedArray mHighLightBiasesStrong;
     @Mock private TypedArray mAmbientColorTemperatures;
     @Mock private TypedArray mDisplayColorTemperatures;
     @Mock private TypedArray mStrongAmbientColorTemperatures;
@@ -108,6 +114,10 @@
                 LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
         mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
                 HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperatureStrong,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperatureStrong,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG);
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceAmbientColorTemperatures))
                 .thenReturn(mAmbientColorTemperatures);
@@ -133,6 +143,18 @@
         when(mResourcesSpy.obtainTypedArray(
                 R.array.config_displayWhiteBalanceHighLightAmbientBiases))
                 .thenReturn(mHighLightBiases);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBrightnessesStrong))
+                .thenReturn(mBrightnessesStrong);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBiasesStrong))
+                .thenReturn(mBiasesStrong);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBrightnessesStrong))
+                .thenReturn(mHighLightBrightnessesStrong);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBiasesStrong))
+                .thenReturn(mHighLightBiasesStrong);
         mockThrottler();
         LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
         LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
@@ -388,8 +410,8 @@
     public void testStrongMode() {
         final float lowerBrightness = 10.0f;
         final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
+        setBrightnessesStrong(lowerBrightness, upperBrightness);
+        setBiasesStrong(0.0f, 1.0f);
         final int ambientColorTempLow = 6000;
         final int ambientColorTempHigh = 8000;
         final int displayColorTempLow = 6400;
@@ -413,7 +435,7 @@
                 setEstimatedBrightnessAndUpdate(controller,
                         mix(lowerBrightness, upperBrightness, brightnessFraction));
                 assertEquals(controller.mPendingAmbientColorTemperature,
-                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE_STRONG,
                                 mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
                                 brightnessFraction),
                         ALLOWED_ERROR_DELTA);
@@ -458,7 +480,7 @@
         assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
     }
 
-    void mockThrottler() {
+    private void mockThrottler() {
         when(mResourcesSpy.getInteger(
                 R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
         when(mResourcesSpy.getInteger(
@@ -513,10 +535,18 @@
         setFloatArrayResource(mBrightnesses, vals);
     }
 
+    private void setBrightnessesStrong(float... vals) {
+        setFloatArrayResource(mBrightnessesStrong, vals);
+    }
+
     private void setBiases(float... vals) {
         setFloatArrayResource(mBiases, vals);
     }
 
+    private void setBiasesStrong(float... vals) {
+        setFloatArrayResource(mBiasesStrong, vals);
+    }
+
     private void setHighLightBrightnesses(float... vals) {
         setFloatArrayResource(mHighLightBrightnesses, vals);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 78e5a42..bdbf4ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -63,6 +63,8 @@
 import org.junit.Test;
 import org.mockito.Mock;
 
+import java.io.File;
+
 /**
  * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
  */
@@ -107,6 +109,8 @@
             .getTargetContext();
     private final SparseArray<UserData> mUsers = new SparseArray<>();
 
+    private File mTestDir;
+
     private Context mSpiedContext;
 
     private @Mock PackageManagerService mMockPms;
@@ -144,17 +148,23 @@
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
 
         // Must construct UserManagerService in the UiThread
+        mTestDir = new File(mRealContext.getDataDir(), "umstest");
+        mTestDir.mkdirs();
         mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
-                mPackagesLock, mRealContext.getDataDir(), mUsers);
+                mPackagesLock, mTestDir, mUsers);
         mUmi = LocalServices.getService(UserManagerInternal.class);
         assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
                 .isNotNull();
     }
 
     @After
-    public void resetUserManagerInternal() {
+    public void tearDown() {
         // LocalServices follows the "Highlander rule" - There can be only one!
         LocalServices.removeServiceForTest(UserManagerInternal.class);
+
+        // Clean up test dir to remove persisted user files.
+        assertThat(deleteRecursive(mTestDir)).isTrue();
+        mUsers.clear();
     }
 
     @Test
@@ -496,6 +506,14 @@
 
     @Test
     public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+        // Remove the main user so we can add another one
+        for (int i = 0; i < mUsers.size(); i++) {
+            UserData userData = mUsers.valueAt(i);
+            if (userData.info.isMain()) {
+                mUsers.delete(i);
+                break;
+            }
+        }
         UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
                 UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
 
@@ -621,6 +639,18 @@
         userData.mLastEnteredForegroundTimeMillis = timeMillis;
     }
 
+    public boolean deleteRecursive(File file) {
+        if (file.isDirectory()) {
+            for (File item : file.listFiles()) {
+                boolean success = deleteRecursive(item);
+                if (!success) {
+                    return false;
+                }
+            }
+        }
+        return file.delete();
+    }
+
     private static final class TestUserData extends UserData {
 
         @SuppressWarnings("deprecation")
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 285a20c..746fb53 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -36,6 +36,9 @@
 import android.content.res.Resources;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 import com.android.server.biometrics.sensors.BiometricNotification;
@@ -47,6 +50,8 @@
 
 import java.io.File;
 
+@Presubmit
+@SmallTest
 public class AuthenticationStatsCollectorTest {
 
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
index 455625c..dde2a3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -29,6 +29,9 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -45,6 +48,8 @@
 import java.util.List;
 import java.util.Set;
 
+@Presubmit
+@SmallTest
 public class AuthenticationStatsPersisterTest {
 
     @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@@ -211,6 +216,20 @@
         assertThat(mStringSetArgumentCaptor.getValue()).contains(expectedFrrStats);
     }
 
+    @Test
+    public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats)));
+
+        mAuthenticationStatsPersister.removeFrrStats(USER_ID_1);
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue()).doesNotContain(authenticationStats);
+    }
+
     private String buildFrrStats(AuthenticationStats authenticationStats)
             throws JSONException {
         if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FACE) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index cb659b6..ecd35a5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1565,6 +1565,25 @@
         assertThat(userInfo.name).isEqualTo(newName);
     }
 
+    @Test
+    public void testCannotCreateAdditionalMainUser() {
+        UserHandle mainUser = mUserManager.getMainUser();
+        assumeTrue("There is no main user", mainUser != null);
+
+        // Users with FLAG_MAIN can't be removed, so no point using the local createUser method.
+        UserInfo newMainUser = mUserManager.createUser("test", UserInfo.FLAG_MAIN);
+        assertThat(newMainUser).isNull();
+
+        List<UserInfo> users = mUserManager.getUsers();
+        int mainUserCount = 0;
+        for (UserInfo user : users) {
+            if (user.isMain()) {
+                mainUserCount++;
+            }
+        }
+        assertThat(mainUserCount).isEqualTo(1);
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         try {
             return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index fae92d9..f83a1df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -36,7 +36,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -44,7 +44,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
@@ -76,6 +75,7 @@
 
 import com.android.internal.R;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.LocalServices;
 import com.android.server.UiServiceTestCase;
 import com.android.server.uri.UriGrantsManagerInternal;
 
@@ -850,84 +850,78 @@
 
     @Test
     public void testCalculateGrantableUris_PappProvided() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.setSound(null, null);
         Notification n = new Notification.Builder(mContext, channel.getId())
                 .setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something")))
                 .build();
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
-        record.mUgmInternal = ugm;
 
-        try {
-            record.calculateGrantableUris();
-            fail("App provided uri for p targeting app should throw exception");
-        } catch (SecurityException e) {
-            // expected
-        }
+        assertThrows("App provided uri for p targeting app should throw exception",
+                SecurityException.class,
+                () -> new NotificationRecord(mMockContext, sbn, channel));
     }
 
     @Test
     public void testCalculateGrantableUris_PappProvided_invalidSound() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class));
 
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
-        record.mUgmInternal = ugm;
 
-        record.calculateGrantableUris();
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
     }
 
     @Test
     public void testCalculateGrantableUris_PuserOverridden() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
 
-        record.calculateGrantableUris();
+        new NotificationRecord(mMockContext, sbn, channel); // should not throw
     }
 
     @Test
     public void testCalculateGrantableUris_prePappProvided() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_O, PKG_O, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
 
-        record.calculateGrantableUris();
-        // should not throw
+        new NotificationRecord(mMockContext, sbn, channel); // should not throw
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 943d8d6..4a541da 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3388,7 +3388,11 @@
     public void onAbort() {}
 
     /**
-     * Notifies this Connection of a request to hold.
+     * Notifies this Connection of a request to hold. {@link Connection#setOnHold} should be within
+     * the onHold() body in order to transition the call state to {@link Connection#STATE_HOLDING}.
+     * <p>
+     * Note: If the Connection does not transition to  {@link Connection#STATE_HOLDING} within 2
+     * seconds, the call will be disconnected.
      */
     public void onHold() {}
 
diff --git a/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
new file mode 100644
index 0000000..d8113fc
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FocusEventDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FocusEventDebugViewTest {
+
+    private FocusEventDebugView mFocusEventDebugView;
+    private FocusEventDebugView.RotaryInputValueView mRotaryInputValueView;
+    private float mScaledVerticalScrollFactor;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        mScaledVerticalScrollFactor =
+                ViewConfiguration.get(context).getScaledVerticalScrollFactor();
+        InputManagerService mockService = mock(InputManagerService.class);
+        when(mockService.monitorInput(anyString(), anyInt()))
+                .thenReturn(InputChannel.openInputChannelPair("FocusEventDebugViewTest")[1]);
+
+        mRotaryInputValueView = new FocusEventDebugView.RotaryInputValueView(context);
+        mFocusEventDebugView = new FocusEventDebugView(context, mockService,
+                () -> mRotaryInputValueView);
+    }
+
+    @Test
+    public void startsRotaryInputValueViewWithDefaultValue() {
+        assertEquals("+0.0", mRotaryInputValueView.getText());
+    }
+
+    @Test
+    public void handleRotaryInput_updatesRotaryInputValueViewWithScrollValue() {
+        mFocusEventDebugView.handleUpdateShowRotaryInput(true);
+
+        mFocusEventDebugView.handleRotaryInput(createRotaryMotionEvent(0.5f));
+
+        assertEquals(String.format("+%.1f", 0.5f * mScaledVerticalScrollFactor),
+                mRotaryInputValueView.getText());
+    }
+
+    @Test
+    public void updateActivityStatus_setsAndRemovesColorFilter() {
+        // It should not be active initially.
+        assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+        mRotaryInputValueView.updateActivityStatus(true);
+        // It should be active after rotary input.
+        assertNotNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+        mRotaryInputValueView.updateActivityStatus(false);
+        // It should not be active after waiting for mUpdateActivityStatusCallback.
+        assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+    }
+
+    private MotionEvent createRotaryMotionEvent(float scrollAxisValue) {
+        PointerCoords pointerCoords = new PointerCoords();
+        pointerCoords.setAxisValue(MotionEvent.AXIS_SCROLL, scrollAxisValue);
+        PointerProperties pointerProperties = new PointerProperties();
+
+        return MotionEvent.obtain(
+                /* downTime */ 0,
+                /* eventTime */ 0,
+                /* action */ MotionEvent.ACTION_SCROLL,
+                /* pointerCount */ 1,
+                /* pointerProperties */ new PointerProperties[] {pointerProperties},
+                /* pointerCoords */ new PointerCoords[] {pointerCoords},
+                /* metaState */ 0,
+                /* buttonState */ 0,
+                /* xPrecision */ 0,
+                /* yPrecision */ 0,
+                /* deviceId */ 0,
+                /* edgeFlags */ 0,
+                /* source */ InputDevice.SOURCE_ROTARY_ENCODER,
+                /* flags */ 0
+        );
+    }
+}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
index 31ea832..c92d768 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
@@ -16,10 +16,6 @@
 
 package com.android.test.silkfx.hdr
 
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
-import android.animation.ValueAnimator.AnimatorUpdateListener
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Canvas
@@ -46,7 +42,7 @@
     private var selectedImage = -1
     private var outputMode = R.id.output_hdr
     private var bitmap: Bitmap? = null
-    private var gainmap: Gainmap? = null
+    private var originalGainmap: Gainmap? = null
     private var gainmapVisualizer: Bitmap? = null
     private lateinit var imageView: SubsamplingScaleImageView
     private lateinit var gainmapMetadataEditor: GainmapMetadataEditor
@@ -70,7 +66,6 @@
             it.check(outputMode)
             it.setOnCheckedChangeListener { _, checkedId ->
                 outputMode = checkedId
-                // Intentionally don't do anything fancy so that mode A/B comparisons are easy
                 updateDisplay()
             }
         }
@@ -101,41 +96,10 @@
 
         imageView.apply {
             isClickable = true
-            // Example of animating between SDR and HDR using gainmap params; animates HDR->SDR->HDR
-            // with a brief pause on SDR. The key thing here is that the gainmap's
-            // minDisplayRatioForHdrTransition is animated between its original value (for full HDR)
-            // and displayRatioForFullHdr (for full SDR). The view must also be invalidated during
-            // the animation for the updates to take effect.
             setOnClickListener {
-                if (gainmap != null && (outputMode == R.id.output_hdr ||
-                                        outputMode == R.id.output_hdr_test)) {
-                    val animationLengthMs: Long = 500
-                    val updateListener = object : AnimatorUpdateListener {
-                        override fun onAnimationUpdate(animation: ValueAnimator) {
-                            imageView.invalidate()
-                        }
-                    }
-                    val hdrToSdr = ObjectAnimator.ofFloat(
-                      gainmap, "minDisplayRatioForHdrTransition",
-                      gainmap!!.minDisplayRatioForHdrTransition,
-                      gainmap!!.displayRatioForFullHdr).apply {
-                        duration = animationLengthMs
-                        addUpdateListener(updateListener)
-                    }
-                    val sdrToHdr = ObjectAnimator.ofFloat(
-                      gainmap, "minDisplayRatioForHdrTransition",
-                      gainmap!!.displayRatioForFullHdr,
-                      gainmap!!.minDisplayRatioForHdrTransition).apply {
-                        duration = animationLengthMs
-                        addUpdateListener(updateListener)
-                    }
-
-                    AnimatorSet().apply {
-                        play(hdrToSdr)
-                        play(sdrToHdr).after(animationLengthMs)
-                        start()
-                    }
-                }
+                animate().alpha(.5f).withEndAction {
+                    animate().alpha(1f).start()
+                }.start()
             }
         }
     }
@@ -149,7 +113,7 @@
     }
 
     private fun doDecode(source: ImageDecoder.Source) {
-        gainmap = null
+        originalGainmap = null
         bitmap = ImageDecoder.decodeBitmap(source) { decoder, info, source ->
             decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
         }
@@ -167,9 +131,10 @@
             findViewById<TextView>(R.id.error_msg)!!.visibility = View.GONE
             findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE
 
-            gainmap = bitmap!!.gainmap
-            gainmapMetadataEditor.setGainmap(gainmap)
-            val map = gainmap!!.gainmapContents
+            val gainmap = bitmap!!.gainmap!!
+            originalGainmap = gainmap
+            gainmapMetadataEditor.setGainmap(Gainmap(gainmap, gainmap.gainmapContents))
+            val map = gainmap.gainmapContents
             if (map.config != Bitmap.Config.ALPHA_8) {
                 gainmapVisualizer = map
             } else {
@@ -198,14 +163,12 @@
 
         imageView.setImage(ImageSource.cachedBitmap(when (outputMode) {
             R.id.output_hdr -> {
-                gainmapMetadataEditor.useOriginalMetadata()
-                bitmap!!.gainmap = gainmap
+                bitmap!!.gainmap = originalGainmap
                 bitmap!!
             }
 
             R.id.output_hdr_test -> {
-                gainmapMetadataEditor.useEditMetadata()
-                bitmap!!.gainmap = gainmap
+                bitmap!!.gainmap = gainmapMetadataEditor.editedGainmap()
                 bitmap!!
             }
 
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
index 8a65304..c4bc600 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
@@ -28,23 +28,27 @@
 import com.android.test.silkfx.R
 
 data class GainmapMetadata(
-    var ratioMin: Float,
-    var ratioMax: Float,
-    var capacityMin: Float,
-    var capacityMax: Float,
-    var gamma: Float,
-    var offsetSdr: Float,
-    var offsetHdr: Float
+        var ratioMin: Float,
+        var ratioMax: Float,
+        var capacityMin: Float,
+        var capacityMax: Float,
+        var gamma: Float,
+        var offsetSdr: Float,
+        var offsetHdr: Float
 )
 
+/**
+ * Note: This can only handle single-channel gainmaps nicely. It will force all 3-channel
+ * metadata to have the same value single value and is not intended to be a robust demonstration
+ * of gainmap metadata editing
+ */
 class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
-    private var gainmap: Gainmap? = null
-    private var showingEdits = false
+    private lateinit var gainmap: Gainmap
 
     private var metadataPopup: PopupWindow? = null
 
     private var originalMetadata: GainmapMetadata = GainmapMetadata(
-        1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
+            1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
     private var currentMetadata: GainmapMetadata = originalMetadata.copy()
 
     private val maxProgress = 100.0f
@@ -61,23 +65,18 @@
     private val maxGamma = 3.0f
     // Min and max offsets are 0.0 and 1.0 respectively
 
-    fun setGainmap(newGainmap: Gainmap?) {
+    fun setGainmap(newGainmap: Gainmap) {
         gainmap = newGainmap
-        originalMetadata = GainmapMetadata(gainmap!!.getRatioMin()[0],
-            gainmap!!.getRatioMax()[0], gainmap!!.getMinDisplayRatioForHdrTransition(),
-            gainmap!!.getDisplayRatioForFullHdr(), gainmap!!.getGamma()[0],
-            gainmap!!.getEpsilonSdr()[0], gainmap!!.getEpsilonHdr()[0])
+        originalMetadata = GainmapMetadata(gainmap.getRatioMin()[0],
+                gainmap.getRatioMax()[0], gainmap.getMinDisplayRatioForHdrTransition(),
+                gainmap.getDisplayRatioForFullHdr(), gainmap.getGamma()[0],
+                gainmap.getEpsilonSdr()[0], gainmap.getEpsilonHdr()[0])
         currentMetadata = originalMetadata.copy()
     }
 
-    fun useOriginalMetadata() {
-        showingEdits = false
-        applyMetadata(originalMetadata)
-    }
-
-    fun useEditMetadata() {
-        showingEdits = true
+    fun editedGainmap(): Gainmap {
         applyMetadata(currentMetadata)
+        return gainmap
     }
 
     fun closeEditor() {
@@ -93,7 +92,7 @@
         val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null)
 
         metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT)
+                ViewGroup.LayoutParams.WRAP_CONTENT)
         metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0)
 
         (view.getParent() as ViewGroup).removeView(view)
@@ -117,7 +116,7 @@
         val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
         val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
         arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek,
-            offsetSdrSeek, offsetHdrSeek).forEach {
+                offsetSdrSeek, offsetHdrSeek).forEach {
             it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                     if (!fromUser) return
@@ -149,37 +148,37 @@
         val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
 
         gainmapMinSeek.setProgress(
-            ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
+                ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
         gainmapMaxSeek.setProgress(
-            ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
+                ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
         capacityMinSeek.setProgress(
-            ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
+                ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
         capacityMaxSeek.setProgress(
-            ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
+                ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
         gammaSeek.setProgress(
-            ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
+                ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
         // Log base 3 via: log_b(x) = log_y(x) / log_y(b)
         offsetSdrSeek.setProgress(
-            ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
-             .toFloat() * maxProgress).toInt())
+                ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
+                        .toFloat() * maxProgress).toInt())
         offsetHdrSeek.setProgress(
-            ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
-             .toFloat() * maxProgress).toInt())
+                ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
+                        .toFloat() * maxProgress).toInt())
 
         parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
-            "%.3f".format(currentMetadata.ratioMin))
+                "%.3f".format(currentMetadata.ratioMin))
         parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
-            "%.3f".format(currentMetadata.ratioMax))
+                "%.3f".format(currentMetadata.ratioMax))
         parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
-            "%.3f".format(currentMetadata.capacityMin))
+                "%.3f".format(currentMetadata.capacityMin))
         parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
-            "%.3f".format(currentMetadata.capacityMax))
+                "%.3f".format(currentMetadata.capacityMax))
         parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
-            "%.3f".format(currentMetadata.gamma))
+                "%.3f".format(currentMetadata.gamma))
         parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
-            "%.5f".format(currentMetadata.offsetSdr))
+                "%.5f".format(currentMetadata.offsetSdr))
         parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
-            "%.5f".format(currentMetadata.offsetHdr))
+                "%.5f".format(currentMetadata.offsetHdr))
     }
 
     private fun resetGainmapMetadata() {
@@ -189,69 +188,59 @@
     }
 
     private fun applyMetadata(newMetadata: GainmapMetadata) {
-        gainmap!!.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
-        gainmap!!.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
-        gainmap!!.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
-        gainmap!!.setDisplayRatioForFullHdr(newMetadata.capacityMax)
-        gainmap!!.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
-        gainmap!!.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
-        gainmap!!.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
+        gainmap.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
+        gainmap.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
+        gainmap.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
+        gainmap.setDisplayRatioForFullHdr(newMetadata.capacityMax)
+        gainmap.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
+        gainmap.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
+        gainmap.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
         renderView.invalidate()
     }
 
     private fun updateGainmapMin(normalized: Float) {
         val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin)
         parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
-            "%.3f".format(newValue))
+                "%.3f".format(newValue))
         currentMetadata.ratioMin = newValue
-        if (showingEdits) {
-            gainmap!!.setRatioMin(newValue, newValue, newValue)
-            renderView.invalidate()
-        }
+        gainmap.setRatioMin(newValue, newValue, newValue)
+        renderView.invalidate()
     }
 
     private fun updateGainmapMax(normalized: Float) {
         val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax)
         parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
-            "%.3f".format(newValue))
+                "%.3f".format(newValue))
         currentMetadata.ratioMax = newValue
-        if (showingEdits) {
-            gainmap!!.setRatioMax(newValue, newValue, newValue)
-            renderView.invalidate()
-        }
+        gainmap.setRatioMax(newValue, newValue, newValue)
+        renderView.invalidate()
     }
 
     private fun updateCapacityMin(normalized: Float) {
         val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin)
         parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
-            "%.3f".format(newValue))
+                "%.3f".format(newValue))
         currentMetadata.capacityMin = newValue
-        if (showingEdits) {
-            gainmap!!.setMinDisplayRatioForHdrTransition(newValue)
-            renderView.invalidate()
-        }
+        gainmap.setMinDisplayRatioForHdrTransition(newValue)
+        renderView.invalidate()
     }
 
     private fun updateCapacityMax(normalized: Float) {
         val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax)
         parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
-            "%.3f".format(newValue))
+                "%.3f".format(newValue))
         currentMetadata.capacityMax = newValue
-        if (showingEdits) {
-            gainmap!!.setDisplayRatioForFullHdr(newValue)
-            renderView.invalidate()
-        }
+        gainmap.setDisplayRatioForFullHdr(newValue)
+        renderView.invalidate()
     }
 
     private fun updateGamma(normalized: Float) {
         val newValue = minGamma + normalized * (maxGamma - minGamma)
         parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
-            "%.3f".format(newValue))
+                "%.3f".format(newValue))
         currentMetadata.gamma = newValue
-        if (showingEdits) {
-            gainmap!!.setGamma(newValue, newValue, newValue)
-            renderView.invalidate()
-        }
+        gainmap.setGamma(newValue, newValue, newValue)
+        renderView.invalidate()
     }
 
     private fun updateOffsetSdr(normalized: Float) {
@@ -260,12 +249,10 @@
             newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
         }
         parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
-            "%.5f".format(newValue))
+                "%.5f".format(newValue))
         currentMetadata.offsetSdr = newValue
-        if (showingEdits) {
-            gainmap!!.setEpsilonSdr(newValue, newValue, newValue)
-            renderView.invalidate()
-        }
+        gainmap.setEpsilonSdr(newValue, newValue, newValue)
+        renderView.invalidate()
     }
 
     private fun updateOffsetHdr(normalized: Float) {
@@ -274,11 +261,9 @@
             newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
         }
         parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
-            "%.5f".format(newValue))
+                "%.5f".format(newValue))
         currentMetadata.offsetHdr = newValue
-        if (showingEdits) {
-            gainmap!!.setEpsilonHdr(newValue, newValue, newValue)
-            renderView.invalidate()
-        }
+        gainmap.setEpsilonHdr(newValue, newValue, newValue)
+        renderView.invalidate()
     }
 }