Merge "Add additonal logging around user switching in KeyguardUpdateMonitor" into udc-qpr-dev
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 60b11b4..7b5dd55 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -807,9 +807,11 @@
             onUnbindInput();
             mInputBinding = null;
             mInputConnection = null;
-            // free-up cached InkWindow surface on detaching from current client.
+
             if (mInkWindow != null) {
-                removeHandwritingInkWindow();
+                finishStylusHandwriting();
+                // free-up InkWindow surface after timeout.
+                scheduleStylusWindowIdleTimeout();
             }
         }
 
@@ -1020,6 +1022,7 @@
                 mOnPreparedStylusHwCalled = true;
             }
             if (onStartStylusHandwriting()) {
+                cancelStylusWindowIdleTimeout();
                 mPrivOps.onStylusHandwritingReady(requestId, Process.myPid());
             } else {
                 Log.i(TAG, "IME is not ready. Can't start Stylus Handwriting");
@@ -1109,7 +1112,7 @@
          */
         @Override
         public void removeStylusHandwritingWindow() {
-            InputMethodService.this.removeStylusHandwritingWindow();
+            InputMethodService.this.finishAndRemoveStylusHandwritingWindow();
         }
 
         /**
@@ -2667,21 +2670,15 @@
      * Typically, this is called when {@link InkWindow} should no longer be holding a surface in
      * memory.
      */
-    private void removeStylusHandwritingWindow() {
+    private void finishAndRemoveStylusHandwritingWindow() {
+        cancelStylusWindowIdleTimeout();
+        mOnPreparedStylusHwCalled = false;
+        mStylusWindowIdleTimeoutRunnable = null;
         if (mInkWindow != null) {
             if (mHandwritingRequestId.isPresent()) {
                 // if handwriting session is still ongoing. This shouldn't happen.
                 finishStylusHandwriting();
             }
-            removeHandwritingInkWindow();
-        }
-    }
-
-    private void removeHandwritingInkWindow() {
-        cancelStylusWindowIdleTimeout();
-        mOnPreparedStylusHwCalled = false;
-        mStylusWindowIdleTimeoutRunnable = null;
-        if (mInkWindow != null) {
             mInkWindow.hide(true /* remove */);
             mInkWindow.destroy();
             mInkWindow = null;
@@ -2707,7 +2704,7 @@
     private Runnable getStylusWindowIdleTimeoutRunnable() {
         if (mStylusWindowIdleTimeoutRunnable == null) {
             mStylusWindowIdleTimeoutRunnable = () -> {
-                removeHandwritingInkWindow();
+                finishAndRemoveStylusHandwritingWindow();
                 mStylusWindowIdleTimeoutRunnable = null;
             };
         }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 00676f3..01e8fea 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -926,16 +926,19 @@
      * @hide
      */
     @VisibleForTesting
-    public final @NonNull String getTransactionTraceName(int transactionCode) {
+    public final @Nullable String getTransactionTraceName(int transactionCode) {
+        final boolean isInterfaceUserDefined = getMaxTransactionId() == 0;
         if (mTransactionTraceNames == null) {
-            final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
+            final int highestId = isInterfaceUserDefined ? TRANSACTION_TRACE_NAME_ID_LIMIT
+                    : Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
             mSimpleDescriptor = getSimpleDescriptor();
             mTransactionTraceNames = new AtomicReferenceArray(highestId + 1);
         }
 
-        final int index = transactionCode - FIRST_CALL_TRANSACTION;
-        if (index < 0 || index >= mTransactionTraceNames.length()) {
-            return mSimpleDescriptor + "#" + transactionCode;
+        final int index = isInterfaceUserDefined
+                ? transactionCode : transactionCode - FIRST_CALL_TRANSACTION;
+        if (index >= mTransactionTraceNames.length() || index < 0) {
+            return null;
         }
 
         String transactionTraceName = mTransactionTraceNames.getAcquire(index);
@@ -1300,19 +1303,9 @@
         final boolean hasFullyQualifiedName = getMaxTransactionId() > 0;
         final String transactionTraceName;
 
-        if (tagEnabled && hasFullyQualifiedName) {
+        if (tagEnabled) {
             // If tracing enabled and we have a fully qualified name, fetch the name
             transactionTraceName = getTransactionTraceName(code);
-        } else if (tagEnabled && isStackTrackingEnabled()) {
-            // If tracing is enabled and we *don't* have a fully qualified name, fetch the
-            // 'best effort' name only for stack tracking. This works around noticeable perf impact
-            // on low latency binder calls (<100us). The tracing call itself is between (1-10us) and
-            // the perf impact can be quite noticeable while benchmarking such binder calls.
-            // The primary culprits are ContentProviders and Cursors which convenienty don't
-            // autogenerate their AIDL and hence will not have a fully qualified name.
-            //
-            // TODO(b/253426478): Relax this constraint after a more robust fix
-            transactionTraceName = getTransactionTraceName(code);
         } else {
             transactionTraceName = null;
         }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index e6bdfe1..299e7f1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,6 +115,7 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+    private static final String SYSTEM_ANGLE_STRING = "system";
 
     private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
 
@@ -195,15 +196,16 @@
     }
 
     /**
-     * Query to determine if ANGLE should be used
+     * Query to determine the ANGLE driver choice.
      */
-    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+    private String queryAngleChoice(Context context, Bundle coreSettings,
+                                               String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
-        return shouldUseAngleInternal(context, coreSettings, packageName);
+        return queryAngleChoiceInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -424,10 +426,11 @@
      *    forces a choice;
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+    private String queryAngleChoiceInternal(Context context, Bundle bundle,
+                                                       String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -442,7 +445,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         }
 
         // Get the per-application settings lists
@@ -465,7 +468,8 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // See if this application is listed in the per-application settings list
@@ -473,7 +477,8 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -484,13 +489,14 @@
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
         if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_NATIVE;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
             // the game mode indicates
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
     }
 
@@ -557,8 +563,12 @@
      */
     private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
-
-        if (!shouldUseAngle(context, bundle, packageName)) {
+        final String angleChoice = queryAngleChoice(context, bundle, packageName);
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
+            return false;
+        }
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+            nativeSetAngleInfo("", true, packageName, null);
             return false;
         }
 
@@ -627,10 +637,10 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
+        // application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, false, packageName, features);
+        nativeSetAngleInfo(paths, false, packageName, features);
 
         return true;
     }
@@ -652,10 +662,10 @@
             return false;
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, system ANGLE will be used.  Call nativeSetAngleInfo() with
+        // the application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo("", true, packageName, features);
+        nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
         return true;
     }
 
@@ -936,8 +946,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
-            String[] features);
+    private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
+            String packageName, String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 1d58268..17afd55 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -69,6 +69,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.inputmethod.ImeTracing;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.util.function.TriFunction;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -77,7 +78,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.BiFunction;
 
 /**
  * Implements {@link WindowInsetsController} on the client.
@@ -627,7 +627,8 @@
     private final InsetsState mLastDispatchedState = new InsetsState();
 
     private final Rect mFrame = new Rect();
-    private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator;
+    private final TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer>
+            mConsumerCreator;
     private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
     private final InsetsSourceConsumer mImeSourceConsumer;
     private final Host mHost;
@@ -695,13 +696,6 @@
 
                     // Don't change the indexes of the sources while traversing. Remove it later.
                     mPendingRemoveIndexes.add(index1);
-
-                    // Remove the consumer as well except the IME one. IME consumer should always
-                    // be there since we need to communicate with InputMethodManager no matter we
-                    // have the source or not.
-                    if (source1.getType() != ime()) {
-                        mSourceConsumers.remove(source1.getId());
-                    }
                 }
 
                 @Override
@@ -756,12 +750,12 @@
             };
 
     public InsetsController(Host host) {
-        this(host, (controller, source) -> {
-            if (source.getType() == ime()) {
-                return new ImeInsetsSourceConsumer(source.getId(), controller.mState,
+        this(host, (controller, id, type) -> {
+            if (type == ime()) {
+                return new ImeInsetsSourceConsumer(id, controller.mState,
                         Transaction::new, controller);
             } else {
-                return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState,
+                return new InsetsSourceConsumer(id, type, controller.mState,
                         Transaction::new, controller);
             }
         }, host.getHandler());
@@ -769,7 +763,7 @@
 
     @VisibleForTesting
     public InsetsController(Host host,
-            BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator,
+            TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> consumerCreator,
             Handler handler) {
         mHost = host;
         mConsumerCreator = consumerCreator;
@@ -821,7 +815,7 @@
         };
 
         // Make mImeSourceConsumer always non-null.
-        mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime()));
+        mImeSourceConsumer = getSourceConsumer(ID_IME, ime());
     }
 
     @VisibleForTesting
@@ -898,7 +892,12 @@
                     cancelledUserAnimationTypes[0] |= type;
                 }
             }
-            getSourceConsumer(source).updateSource(source, animationType);
+            final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
+            if (consumer != null) {
+                consumer.updateSource(source, animationType);
+            } else {
+                mState.addSource(source);
+            }
             existingTypes |= type;
             if (source.isVisible()) {
                 visibleTypes |= type;
@@ -1002,8 +1001,8 @@
 
         @InsetsType int controllableTypes = 0;
         int consumedControlCount = 0;
-        final int[] showTypes = new int[1];
-        final int[] hideTypes = new int[1];
+        final @InsetsType int[] showTypes = new int[1];
+        final @InsetsType int[] hideTypes = new int[1];
 
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1019,15 +1018,12 @@
             consumer.setControl(control, showTypes, hideTypes);
         }
 
+        // Ensure to create source consumers if not available yet.
         if (consumedControlCount != mTmpControlArray.size()) {
-            // Whoops! The server sent us some controls without sending corresponding sources.
             for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
                 final InsetsSourceControl control = mTmpControlArray.valueAt(i);
-                final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId());
-                if (consumer == null) {
-                    control.release(SurfaceControl::release);
-                    Log.e(TAG, control + " has no consumer.");
-                }
+                getSourceConsumer(control.getId(), control.getType())
+                        .setControl(control, showTypes, hideTypes);
             }
         }
 
@@ -1592,6 +1588,11 @@
         if (type == ime()) {
             abortPendingImeControlRequest();
         }
+        if (consumer.getType() != ime()) {
+            // IME consumer should always be there since we need to communicate with
+            // InputMethodManager no matter we have the control or not.
+            mSourceConsumers.remove(consumer.getId());
+        }
     }
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
@@ -1645,21 +1646,20 @@
     }
 
     @VisibleForTesting
-    public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) {
-        final int sourceId = source.getId();
-        InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId);
+    public @NonNull InsetsSourceConsumer getSourceConsumer(int id, int type) {
+        InsetsSourceConsumer consumer = mSourceConsumers.get(id);
         if (consumer != null) {
             return consumer;
         }
-        if (source.getType() == ime() && mImeSourceConsumer != null) {
+        if (type == ime() && mImeSourceConsumer != null) {
             // WindowInsets.Type.ime() should be only provided by one source.
             mSourceConsumers.remove(mImeSourceConsumer.getId());
             consumer = mImeSourceConsumer;
-            consumer.setId(sourceId);
+            consumer.setId(id);
         } else {
-            consumer = mConsumerCreator.apply(this, source);
+            consumer = mConsumerCreator.apply(this, id, type);
         }
-        mSourceConsumers.put(sourceId, consumer);
+        mSourceConsumers.put(id, consumer);
         return consumer;
     }
 
@@ -1668,8 +1668,7 @@
         return mImeSourceConsumer;
     }
 
-    @VisibleForTesting
-    public void notifyVisibilityChanged() {
+    void notifyVisibilityChanged() {
         mHost.notifyInsetsChanged();
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 467d720..34b2884 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -149,9 +149,12 @@
             // Check if we need to restore server visibility.
             final InsetsSource localSource = mState.peekSource(mId);
             final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId);
-            if (localSource != null && serverSource != null
-                    && localSource.isVisible() != serverSource.isVisible()) {
-                localSource.setVisible(serverSource.isVisible());
+            final boolean localVisible = localSource != null && localSource.isVisible();
+            final boolean serverVisible = serverSource != null && serverSource.isVisible();
+            if (localSource != null) {
+                localSource.setVisible(serverVisible);
+            }
+            if (localVisible != serverVisible) {
                 mController.notifyVisibilityChanged();
             }
         } else {
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index d80819f..0e7c6d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -181,7 +181,7 @@
     private static final int TOUCH_SLOP = 8;
 
     /** Distance a stylus touch can wander before we think the user is handwriting in dips. */
-    private static final int HANDWRITING_SLOP = 4;
+    private static final int HANDWRITING_SLOP = 2;
 
     /**
      * Defines the minimum size of the touch target for a scrollbar in dips
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 739c1bf..d729d49 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -4214,6 +4214,14 @@
         }
 
         @Override
+        public void requestHideFillUiWhenDestroyed(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestHideFillUi(id, true));
+            }
+        }
+
+        @Override
         public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 51afe4c..917a974 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -79,6 +79,11 @@
     void requestHideFillUi(int sessionId, in AutofillId id);
 
     /**
+     * Requests hiding the fill UI when it's destroyed
+     */
+    void requestHideFillUiWhenDestroyed(int sessionId, in AutofillId id);
+
+    /**
      * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if
      * sessionFinishedState != 0).
      */
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index afc3cbd..8fc30d1 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
                          jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
                                                      packageNameChars.c_str(), features);
 }
 
@@ -118,7 +118,7 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+        {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8a797c7..d1cca39 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2694,6 +2694,9 @@
          backlight values -->
     <bool name="config_displayBrightnessBucketsInDoze">false</bool>
 
+    <!-- True to skip the fade animation on display off event -->
+    <bool name="config_displayColorFadeDisabled">false</bool>
+
     <!-- Power Management: Specifies whether to decouple the auto-suspend state of the
          device from the display on/off state.
 
@@ -5122,13 +5125,18 @@
          a threshold.
          For example, no higher refresh rate if
              display brightness <= disp0 && ambient brightness <= amb0
-             || display brightness <= disp1 && ambient brightness <= amb1 -->
+             || display brightness <= disp1 && ambient brightness <= amb1
+         Brightness thresholds are paired with lux thresholds - they both have to be met.
+         A negative brightness or lux value means that only one threshold should be used - e.g. if
+         the brightness value is negative, only the lux threshold is applied. -->
+    <!-- Low zone brightness thresholds in the range [0, 255] -->
     <integer-array translatable="false" name="config_brightnessThresholdsOfPeakRefreshRate">
          <!--
            <item>disp0</item>
            <item>disp1</item>
         -->
     </integer-array>
+    <!-- Low zone lux thresholds -->
     <integer-array translatable="false" name="config_ambientThresholdsOfPeakRefreshRate">
          <!--
            <item>amb0</item>
@@ -5149,14 +5157,18 @@
          may have lower display brightness requirements for the flickering is visible.
          For example, fixed refresh rate if
              display brightness >= disp0 && ambient brightness >= amb0
-             || display brightness >= disp1 && ambient brightness >= amb1 -->
+             || display brightness >= disp1 && ambient brightness >= amb1
+         Brightness thresholds are paired with lux thresholds - they both have to be met.
+         A negative brightness or lux value means that only one threshold should be used - e.g. if
+         the brightness value is negative, only the lux threshold is applied. -->
+    <!-- High zone brightness thresholds in the range [0, 255] -->
     <integer-array translatable="false" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate">
          <!--
            <item>disp0</item>
            <item>disp1</item>
         -->
     </integer-array>
-
+    <!-- High zone lux thresholds -->
     <integer-array translatable="false" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate">
          <!--
            <item>amb0</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0fe560e..08c404b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3855,6 +3855,7 @@
   <java-symbol type="bool" name="config_dozeSupportsAodWallpaper" />
   <java-symbol type="bool" name="config_displayBlanksAfterDoze" />
   <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" />
+  <java-symbol type="bool" name="config_displayColorFadeDisabled" />
   <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" />
   <java-symbol type="string" name="config_headlineFontFamily" />
   <java-symbol type="string" name="config_headlineFontFamilyMedium" />
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
index 5f54b09..d0c3470 100644
--- a/core/tests/coretests/src/android/os/AidlTest.java
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -28,12 +28,14 @@
 
     private IAidlTest mRemote;
     private AidlObject mLocal;
+    private NonAutoGeneratedObject mNonAutoGenerated;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mLocal = new AidlObject();
         mRemote = IAidlTest.Stub.asInterface(mLocal);
+        mNonAutoGenerated = new NonAutoGeneratedObject("NonAutoGeneratedObject");
     }
 
     private static boolean check(TestParcelable p, int n, String s) {
@@ -84,6 +86,12 @@
         }
     }
 
+    private static class NonAutoGeneratedObject extends Binder {
+        NonAutoGeneratedObject(String descriptor) {
+            super(descriptor);
+        }
+    }
+
     private static class AidlObject extends IAidlTest.Stub {
         public IInterface queryLocalInterface(String descriptor) {
             // overriding this to return null makes asInterface always
@@ -194,7 +202,7 @@
                 TestParcelable[] a1, TestParcelable[] a2) {
             return null;
         }
-        
+
         public void voidSecurityException() {
             throw new SecurityException("gotcha!");
         }
@@ -396,7 +404,7 @@
         assertEquals("s2[1]", s2[1]);
         assertEquals("s2[2]", s2[2]);
     }
-    
+
     @SmallTest
     public void testVoidSecurityException() throws Exception {
         boolean good = false;
@@ -407,7 +415,7 @@
         }
         assertEquals(good, true);
     }
-    
+
     @SmallTest
     public void testIntSecurityException() throws Exception {
         boolean good = false;
@@ -420,7 +428,7 @@
     }
 
     @SmallTest
-    public void testGetTransactionName() throws Exception {
+    public void testGetTransactionNameAutoGenerated() throws Exception {
         assertEquals(15, mLocal.getMaxTransactionId());
 
         assertEquals("booleanArray",
@@ -430,12 +438,21 @@
         assertEquals("parcelableIn",
                 mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_parcelableIn));
 
-        assertEquals("IAidlTest:booleanArray",
+        assertEquals("AIDL::java::IAidlTest::booleanArray::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_booleanArray));
-        assertEquals("IAidlTest:voidSecurityException",
+        assertEquals("AIDL::java::IAidlTest::voidSecurityException::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
-        assertEquals("IAidlTest:parcelableIn",
+        assertEquals("AIDL::java::IAidlTest::parcelableIn::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn));
     }
-}
 
+    @SmallTest
+    public void testGetTransactionNameNonAutoGenerated() throws Exception {
+        assertEquals(0, mNonAutoGenerated.getMaxTransactionId());
+
+        assertEquals("AIDL::java::NonAutoGeneratedObject::#0::server",
+                mNonAutoGenerated.getTransactionTraceName(0));
+        assertEquals("AIDL::java::NonAutoGeneratedObject::#1::server",
+                mNonAutoGenerated.getTransactionTraceName(1));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 0692052..b8f0d5c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -131,10 +131,10 @@
             mTestClock = new OffsettableClock();
             mTestHandler = new TestHandler(null, mTestClock);
             mTestHost = spy(new TestHost(mViewRoot));
-            mController = new InsetsController(mTestHost, (controller, source) -> {
-                if (source.getType() == ime()) {
-                    return new InsetsSourceConsumer(source.getId(), source.getType(),
-                            controller.getState(), Transaction::new, controller) {
+            mController = new InsetsController(mTestHost, (controller, id, type) -> {
+                if (type == ime()) {
+                    return new InsetsSourceConsumer(id, type, controller.getState(),
+                            Transaction::new, controller) {
 
                         private boolean mImeRequestedShow;
 
@@ -150,8 +150,8 @@
                         }
                     };
                 } else {
-                    return new InsetsSourceConsumer(source.getId(), source.getType(),
-                            controller.getState(), Transaction::new, controller);
+                    return new InsetsSourceConsumer(id, type, controller.getState(),
+                            Transaction::new, controller);
                 }
             }, mTestHandler);
             final Rect rect = new Rect(5, 5, 5, 5);
@@ -182,7 +182,8 @@
     @Test
     public void testControlsChanged() {
         mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
-        assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash());
+        assertNotNull(
+                mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl().getLeash());
         mController.addOnControllableInsetsChangedListener(
                 ((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
     }
@@ -194,7 +195,7 @@
         mController.addOnControllableInsetsChangedListener(listener);
         mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
         mController.onControlsChanged(new InsetsSourceControl[0]);
-        assertNull(mController.getSourceConsumer(mStatusSource).getControl());
+        assertNull(mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl());
         InOrder inOrder = Mockito.inOrder(listener);
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(0));
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(statusBars()));
@@ -254,7 +255,7 @@
         // only the original thread that created view hierarchy can touch its views
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             // When using the animation thread, this must not invoke onReady()
@@ -271,7 +272,7 @@
         prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.show(all());
@@ -284,7 +285,7 @@
             mController.hide(all());
             mController.cancelExistingAnimations();
             assertEquals(0, mController.getRequestedVisibleTypes() & types);
-            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -294,14 +295,14 @@
         InsetsSourceControl ime = createControl(ID_IME, ime());
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, ime()));
-            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -914,7 +915,7 @@
             // Simulate IME insets is not controllable
             mController.onControlsChanged(new InsetsSourceControl[0]);
             final InsetsSourceConsumer imeInsetsConsumer =
-                    mController.getSourceConsumer(mImeSource);
+                    mController.getSourceConsumer(ID_IME, ime());
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 988e690..655cb45 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -219,10 +219,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             InsetsState state = new InsetsState();
             ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
-            InsetsController insetsController = new InsetsController(host, (controller, source) -> {
-                if (source.getType() == ime()) {
+            InsetsController insetsController = new InsetsController(host, (ic, id, type) -> {
+                if (type == ime()) {
                     return new InsetsSourceConsumer(ID_IME, ime(), state,
-                            () -> mMockTransaction, controller) {
+                            () -> mMockTransaction, ic) {
                         @Override
                         public int requestShow(boolean fromController,
                                 ImeTracker.Token statsToken) {
@@ -230,11 +230,9 @@
                         }
                     };
                 }
-                return new InsetsSourceConsumer(source.getId(), source.getType(),
-                        controller.getState(), Transaction::new, controller);
+                return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic);
             }, host.getHandler());
-            InsetsSource imeSource = new InsetsSource(ID_IME, ime());
-            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource);
+            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime());
 
             // Initial IME insets source control with its leash.
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 34eac35..8028b14 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -75,7 +75,7 @@
     private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
     private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
     private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
-    private int mHandwritingSlop = 4;
+    private int mHandwritingSlop = 2;
 
     private static final Rect sHwArea1;
     private static final Rect sHwArea2;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 47d58af..9facbd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -31,6 +31,7 @@
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.TvPipModule;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 34a6e0a..422e3b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -20,7 +20,6 @@
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
@@ -45,7 +44,6 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
-import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -76,10 +74,6 @@
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -103,13 +97,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -470,40 +464,6 @@
     }
 
     //
-    // Pip (optional feature)
-    //
-
-    @WMSingleton
-    @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
-    }
-
-    // Needs handler for registering broadcast receivers
-    @WMSingleton
-    @Provides
-    static PipMediaController providePipMediaController(Context context,
-            @ShellMainThread Handler mainHandler) {
-        return new PipMediaController(context, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
-        return new PipSurfaceTransactionHelper(context);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @BindsOptionalOf
-    abstract PipTouchHandler optionalPipTouchHandler();
-
-    //
     // Recent tasks
     //
 
@@ -838,7 +798,6 @@
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f5c6a03..881c8f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.dagger;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.os.Handler;
@@ -46,13 +47,12 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -67,27 +67,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
-import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -127,7 +107,10 @@
  * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
-@Module(includes = WMShellBaseModule.class)
+@Module(includes = {
+        WMShellBaseModule.class,
+        PipModule.class
+})
 public abstract class WMShellModule {
 
     //
@@ -351,195 +334,6 @@
     }
 
     //
-    // Pip
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<Pip> providePip(Context context,
-            ShellInit shellInit,
-            ShellCommandHandler shellCommandHandler,
-            ShellController shellController,
-            DisplayController displayController,
-            PipAnimationController pipAnimationController,
-            PipAppOpsListener pipAppOpsListener,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipMotionHelper pipMotionHelper,
-            PipMediaController pipMediaController,
-            PhonePipMenuController phonePipMenuController,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipTransitionState pipTransitionState,
-            PipTouchHandler pipTouchHandler,
-            PipTransitionController pipTransitionController,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            TaskStackListenerImpl taskStackListener,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            DisplayInsetsController displayInsetsController,
-            TabletopModeController pipTabletopController,
-            Optional<OneHandedController> oneHandedController,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(PipController.create(
-                context, shellInit, shellCommandHandler, shellController,
-                displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
-                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                pipTransitionState, pipTouchHandler, pipTransitionController,
-                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static PipBoundsState providePipBoundsState(Context context,
-            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSnapAlgorithm providePipSnapAlgorithm() {
-        return new PipSnapAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
-    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
-        return new PhonePipKeepClearAlgorithm(context);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
-            PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
-            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipSizeSpecHandler pipSizeSpecHandler) {
-        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
-                pipKeepClearAlgorithm, pipSizeSpecHandler);
-    }
-
-    // Handler is used by Icon.loadDrawableAsync
-    @WMSingleton
-    @Provides
-    static PhonePipMenuController providesPipPhoneMenuController(Context context,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
-            SystemWindows systemWindows,
-            Optional<SplitScreenController> splitScreenOptional,
-            PipUiEventLogger pipUiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
-                systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTouchHandler providePipTouchHandler(Context context,
-            ShellInit shellInit,
-            PhonePipMenuController menuPhoneController,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipMotionHelper pipMotionHelper,
-            FloatingContentCoordinator floatingContentCoordinator,
-            PipUiEventLogger pipUiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
-                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTransitionState providePipTransitionState() {
-        return new PipTransitionState();
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTaskOrganizer providePipTaskOrganizer(Context context,
-            SyncTransactionQueue syncTransactionQueue,
-            PipTransitionState pipTransitionState,
-            PipBoundsState pipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PhonePipMenuController menuPhoneController,
-            PipAnimationController pipAnimationController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            PipTransitionController pipTransitionController,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            Optional<SplitScreenController> splitScreenControllerOptional,
-            DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
-                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
-                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
-                splitScreenControllerOptional, displayController, pipUiEventLogger,
-                shellTaskOrganizer, mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
-            pipSurfaceTransactionHelper) {
-        return new PipAnimationController(pipSurfaceTransactionHelper);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTransitionController providePipTransitionController(Context context,
-            ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
-            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<SplitScreenController> splitScreenOptional) {
-        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
-                splitScreenOptional);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAppOpsListener providePipAppOpsListener(Context context,
-            PipTouchHandler pipTouchHandler,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipMotionHelper providePipMotionHelper(Context context,
-            PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
-            PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
-            PipTransitionController pipTransitionController,
-            FloatingContentCoordinator floatingContentCoordinator) {
-        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
-                menuController, pipSnapAlgorithm, pipTransitionController,
-                floatingContentCoordinator);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
-        return new PipParamsChangedForwarder();
-    }
-
-    //
     // Transitions
     //
 
@@ -548,7 +342,7 @@
     static DefaultMixedHandler provideDefaultMixedHandler(
             ShellInit shellInit,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsTransitionHandler,
             KeyguardTransitionHandler keyguardTransitionHandler,
             Optional<DesktopModeController> desktopModeController,
@@ -556,8 +350,9 @@
             Optional<UnfoldTransitionHandler> unfoldHandler,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
-                pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
-                desktopModeController, desktopTasksController, unfoldHandler);
+                pipTransitionController, recentsTransitionHandler,
+                keyguardTransitionHandler, desktopModeController, desktopTasksController,
+                unfoldHandler);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
new file mode 100644
index 0000000..d972f48
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -0,0 +1,271 @@
+/*
+ * 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.wm.shell.dagger.pip;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.Optional;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip}, this implementation is meant to be
+ * replaced by the sibling {@link Pip2Module}.
+ */
+@Module(includes = {
+        Pip1SharedModule.class,
+        WMShellBaseModule.class
+})
+public abstract class Pip1Module {
+    @WMSingleton
+    @Provides
+    static Optional<Pip> providePip1(Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            ShellController shellController,
+            DisplayController displayController,
+            PipAnimationController pipAnimationController,
+            PipAppOpsListener pipAppOpsListener,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipMotionHelper pipMotionHelper,
+            PipMediaController pipMediaController,
+            PhonePipMenuController phonePipMenuController,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionState pipTransitionState,
+            PipTouchHandler pipTouchHandler,
+            PipTransitionController pipTransitionController,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            TaskStackListenerImpl taskStackListener,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
+            Optional<OneHandedController> oneHandedController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(PipController.create(
+                    context, shellInit, shellCommandHandler, shellController,
+                    displayController, pipAnimationController, pipAppOpsListener,
+                    pipBoundsAlgorithm,
+                    pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+                    pipDisplayLayoutState,
+                    pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                    pipTransitionState, pipTouchHandler, pipTransitionController,
+                    windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+                    displayInsetsController, pipTabletopController, oneHandedController,
+                    mainExecutor));
+        }
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsState providePipBoundsState(Context context,
+            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSnapAlgorithm providePipSnapAlgorithm() {
+        return new PipSnapAlgorithm();
+    }
+
+    @WMSingleton
+    @Provides
+    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+        return new PhonePipKeepClearAlgorithm(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipSizeSpecHandler pipSizeSpecHandler) {
+        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+                pipKeepClearAlgorithm, pipSizeSpecHandler);
+    }
+
+    // Handler is used by Icon.loadDrawableAsync
+    @WMSingleton
+    @Provides
+    static PhonePipMenuController providesPipPhoneMenuController(Context context,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            SystemWindows systemWindows,
+            Optional<SplitScreenController> splitScreenOptional,
+            PipUiEventLogger pipUiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
+                systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTouchHandler providePipTouchHandler(Context context,
+            ShellInit shellInit,
+            PhonePipMenuController menuPhoneController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipMotionHelper pipMotionHelper,
+            FloatingContentCoordinator floatingContentCoordinator,
+            PipUiEventLogger pipUiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+                pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
+                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTransitionState providePipTransitionState() {
+        return new PipTransitionState();
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTaskOrganizer providePipTaskOrganizer(Context context,
+            SyncTransactionQueue syncTransactionQueue,
+            PipTransitionState pipTransitionState,
+            PipBoundsState pipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PhonePipMenuController menuPhoneController,
+            PipAnimationController pipAnimationController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            PipTransitionController pipTransitionController,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            DisplayController displayController,
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipTaskOrganizer(context,
+                syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
+                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenControllerOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
+    }
+
+    @WMSingleton
+    @Provides
+    @Nullable
+    static PipTransition providePipTransition(Context context,
+            ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
+            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenOptional) {
+        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                splitScreenOptional);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAppOpsListener providePipAppOpsListener(Context context,
+            PipTouchHandler pipTouchHandler,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipMotionHelper providePipMotionHelper(Context context,
+            PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
+            PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
+            PipTransitionController pipTransitionController,
+            FloatingContentCoordinator floatingContentCoordinator) {
+        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+                menuController, pipSnapAlgorithm, pipTransitionController,
+                floatingContentCoordinator);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+        return new PipParamsChangedForwarder();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
new file mode 100644
index 0000000..f29b3a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -0,0 +1,66 @@
+/*
+ * 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.wm.shell.dagger.pip;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipUiEventLogger;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides shared dependencies from {@link com.android.wm.shell.pip}, this implementation is
+ * shared with {@link TvPipModule} and possibly other form factors.
+ */
+@Module
+public abstract class Pip1SharedModule {
+    @WMSingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    // Needs handler for registering broadcast receivers
+    @WMSingleton
+    @Provides
+    static PipMediaController providePipMediaController(Context context,
+            @ShellMainThread Handler mainHandler) {
+        return new PipMediaController(context, mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+        return new PipSurfaceTransactionHelper(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
new file mode 100644
index 0000000..c7c6e8a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -0,0 +1,39 @@
+/*
+ * 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.wm.shell.dagger.pip;
+
+import android.annotation.Nullable;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.PipTransition;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
+ * the successor of its sibling {@link Pip1SharedModule}.
+ */
+@Module
+public abstract class Pip2Module {
+    @WMSingleton
+    @Provides
+    @Nullable
+    static PipTransition providePipTransition() {
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
new file mode 100644
index 0000000..2ded4a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -0,0 +1,45 @@
+/*
+ * 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.wm.shell.dagger.pip;
+
+import android.annotation.Nullable;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipUtils;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies for external components / modules reference PiP and extracts away the
+ * selection of legacy and new PiP implementation.
+ */
+@Module(includes = {
+        Pip1Module.class,
+        Pip2Module.class
+})
+public abstract class PipModule {
+    @WMSingleton
+    @Provides
+    @Nullable
+    static PipTransitionController providePipTransitionController(
+            com.android.wm.shell.pip.PipTransition legacyPipTransition,
+            com.android.wm.shell.pip2.PipTransition newPipTransition) {
+        return PipUtils.isPip2ExperimentEnabled() ? newPipTransition : legacyPipTransition;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 14daae0..360bf8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.dagger;
+package com.android.wm.shell.dagger.pip;
 
 import android.content.Context;
 import android.os.Handler;
@@ -28,6 +28,8 @@
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipAppOpsListener;
@@ -62,7 +64,9 @@
 /**
  * Provides TV specific dependencies for Pip.
  */
-@Module(includes = {WMShellBaseModule.class})
+@Module(includes = {
+        WMShellBaseModule.class,
+        Pip1SharedModule.class})
 public abstract class TvPipModule {
     @WMSingleton
     @Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index f34d2a8..a9aa6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -67,4 +67,11 @@
      * view hierarchy or destroyed.
      */
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+    /**
+     * @return {@link PipTransitionController} instance.
+     */
+    default PipTransitionController getPipTransitionController() {
+        return null;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 2590cab..ddffb5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -150,13 +150,15 @@
         mContext = context;
         mMainHandler = mainHandler;
         mHandlerExecutor = new HandlerExecutor(mMainHandler);
-        IntentFilter mediaControlFilter = new IntentFilter();
-        mediaControlFilter.addAction(ACTION_PLAY);
-        mediaControlFilter.addAction(ACTION_PAUSE);
-        mediaControlFilter.addAction(ACTION_NEXT);
-        mediaControlFilter.addAction(ACTION_PREV);
-        mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
-                SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            IntentFilter mediaControlFilter = new IntentFilter();
+            mediaControlFilter.addAction(ACTION_PLAY);
+            mediaControlFilter.addAction(ACTION_PAUSE);
+            mediaControlFilter.addAction(ACTION_NEXT);
+            mediaControlFilter.addAction(ACTION_PREV);
+            mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
+                    SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
+        }
 
         // Creates the standard media buttons that we may show.
         mPauseAction = getDefaultRemoteAction(R.string.pip_pause,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 08da485..208f9b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -364,13 +364,15 @@
         mMainExecutor = mainExecutor;
 
         // TODO: Can be removed once wm components are created on the shell-main thread
-        mMainExecutor.execute(() -> {
-            mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
-        });
-        mTaskOrganizer.addFocusListener(this);
-        mPipTransitionController.setPipOrganizer(this);
-        displayController.addDisplayWindowListener(this);
-        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            mMainExecutor.execute(() -> {
+                mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
+            });
+            mTaskOrganizer.addFocusListener(this);
+            mPipTransitionController.setPipOrganizer(this);
+            displayController.addDisplayWindowListener(this);
+            pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+        }
     }
 
     public PipTransitionController getTransitionController() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6362793..0f74f9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -142,8 +142,10 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipAnimationController = pipAnimationController;
         mTransitions = transitions;
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+                shellInit.addInitCallback(this::onInit, this);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index 8b98790..3cd9848 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
@@ -46,6 +47,9 @@
     // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
     private static final double EPSILON = 1e-7;
 
+    private static final String ENABLE_PIP2_IMPLEMENTATION =
+            "persist.wm.debug.enable_pip2_implementation";
+
     /**
      * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
      * The component name may be null if no such activity exists.
@@ -134,4 +138,8 @@
             return null;
         }
     }
+
+    public static boolean isPip2ExperimentEnabled() {
+        return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 51e7be0..5b9e47f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -461,8 +461,6 @@
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor
     ) {
-
-
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -493,7 +491,9 @@
         mDisplayInsetsController = displayInsetsController;
         mTabletopModeController = tabletopModeController;
 
-        shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -1258,6 +1258,11 @@
                 PipController.this.showPictureInPictureMenu();
             });
         }
+
+        @Override
+        public PipTransitionController getPipTransitionController() {
+            return mPipTransitionController;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 2c4f76b..415f398 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,6 +57,7 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -228,7 +229,9 @@
         // TODO(b/181599115): This should really be initializes as part of the pip controller, but
         // until all PIP implementations derive from the controller, just initialize the touch handler
         // if it is needed
-        shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
new file mode 100644
index 0000000..8ab85d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -0,0 +1,43 @@
+/*
+ * 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.wm.shell.pip2;
+
+import android.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+/** Placeholder, for demonstrate purpose only. */
+public abstract class PipTransition extends PipTransitionController {
+    public PipTransition(
+            @NonNull ShellInit shellInit,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @NonNull Transitions transitions,
+            PipBoundsState pipBoundsState,
+            PipMenuController pipMenuController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController) {
+        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
new file mode 100644
index 0000000..415ea8f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
@@ -0,0 +1,3 @@
+# PLACEHOLDER
+
+This is meant to be the doc for the pip2 package.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index c5c22de..052af3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -49,7 +49,6 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -147,7 +146,7 @@
 
     public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
             Optional<SplitScreenController> splitScreenControllerOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
             KeyguardTransitionHandler keyguardHandler,
             Optional<DesktopModeController> desktopModeControllerOptional,
@@ -155,11 +154,12 @@
             Optional<UnfoldTransitionHandler> unfoldHandler) {
         mPlayer = player;
         mKeyguardHandler = keyguardHandler;
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
+        if (Transitions.ENABLE_SHELL_TRANSITIONS
+                && pipTransitionController != null
                 && splitScreenControllerOptional.isPresent()) {
             // Add after dependencies because it is higher priority
             shellInit.addInitCallback(() -> {
-                mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler();
+                mPipHandler = pipTransitionController;
                 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
                 mPlayer.addHandler(this);
                 if (mSplitHandler != null) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 7085d55..0c6fc56 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -62,7 +62,7 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerMovesAwayFromEdge() {
         flicker.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 2b87766..de64f78 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.flicker.pip
 
 import android.graphics.Rect
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -80,7 +80,7 @@
     /**
      * Checks that the visible region area of [pipApp] moves to closest edge during the animation.
      */
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerMovesToClosestEdge() {
         flicker.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index adc5ee3..0295741 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -16,8 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -35,14 +34,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 270677470)
 class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
 
     /** Checks that the visible region area of [pipApp] always decreases during the animation. */
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerAreaDecreases() {
         flicker.assertLayers {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 997d1f4..3011d31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -80,6 +80,8 @@
     private Bitmap mSavedPhoto;
     private String mSavedName;
     private Drawable mSavedDrawable;
+    private String mUserName;
+    private Drawable mNewUserIcon;
     private Boolean mIsAdmin;
     private Dialog mUserCreationDialog;
     private View mGrantAdminView;
@@ -89,6 +91,7 @@
     private ActivityStarter mActivityStarter;
     private boolean mWaitingForActivityResult;
     private NewUserData mSuccessCallback;
+    private Runnable mCancelCallback;
 
     private final String mFileAuthority;
 
@@ -113,6 +116,7 @@
         mEditUserInfoView = null;
         mUserNameView = null;
         mSuccessCallback = null;
+        mCancelCallback = null;
         mCurrentState = INITIAL_DIALOG;
     }
 
@@ -184,14 +188,12 @@
         mActivity = activity;
         mCustomDialogHelper = new CustomDialogHelper(activity);
         mSuccessCallback = successCallback;
+        mCancelCallback = cancelCallback;
         mActivityStarter = activityStarter;
         addCustomViews(isMultipleAdminEnabled);
         mUserCreationDialog = mCustomDialogHelper.getDialog();
         updateLayout();
-        mUserCreationDialog.setOnDismissListener(view -> {
-            cancelCallback.run();
-            clear();
-        });
+        mUserCreationDialog.setOnDismissListener(view -> finish());
         mCustomDialogHelper.setMessagePadding(MESSAGE_PADDING);
         mUserCreationDialog.setCanceledOnTouchOutside(true);
         return mUserCreationDialog;
@@ -269,20 +271,14 @@
                 mGrantAdminView.setVisibility(View.GONE);
                 break;
             case CREATE_USER_AND_CLOSE:
-                Drawable newUserIcon = mEditUserPhotoController != null
+                mNewUserIcon = mEditUserPhotoController != null
                         ? mEditUserPhotoController.getNewUserPhotoDrawable()
                         : null;
 
                 String newName = mUserNameView.getText().toString().trim();
                 String defaultName = mActivity.getString(R.string.user_new_user_name);
-                String userName = !newName.isEmpty() ? newName : defaultName;
-
-                if (mSuccessCallback != null) {
-                    mSuccessCallback.onSuccess(userName, newUserIcon,
-                            Boolean.TRUE.equals(mIsAdmin));
-                }
+                mUserName = !newName.isEmpty() ? newName : defaultName;
                 mCustomDialogHelper.getDialog().dismiss();
-                clear();
                 break;
             case EXIT_DIALOG:
                 mCustomDialogHelper.getDialog().dismiss();
@@ -384,4 +380,20 @@
     public boolean isActive() {
         return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null;
     }
+
+    /**
+     * Runs callback and clears saved values after dialog is dismissed.
+     */
+    public void finish() {
+        if (mCurrentState == CREATE_USER_AND_CLOSE) {
+            if (mSuccessCallback != null) {
+                mSuccessCallback.onSuccess(mUserName, mNewUserIcon, Boolean.TRUE.equals(mIsAdmin));
+            }
+        } else {
+            if (mCancelCallback != null) {
+                mCancelCallback.run();
+            }
+        }
+        clear();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index b538077..66a2ea6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -212,6 +212,7 @@
         next.performClick();
         verify(successCallback, times(1))
                 .onSuccess(expectedNewName, null, true);
+        verifyNoInteractions(cancelCallback);
     }
 
     @Test
@@ -233,6 +234,7 @@
         next.performClick();
         verify(successCallback, times(1))
                 .onSuccess(expectedNewName, null, false);
+        verifyNoInteractions(cancelCallback);
     }
 
     private class TestCreateUserDialogController extends CreateUserDialogController {
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index bb8002a..2711dad 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -35,6 +35,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     }
@@ -142,6 +145,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 983b09f..ee9b132 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -439,6 +439,8 @@
     <string name="face_reenroll_failure_dialog_content">Couldn\u2019t set up face unlock. Go to Settings to try again.</string>
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
+    <!-- Content description after successful auth when confirmation required -->
+    <string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string>
     <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
     <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
     <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 9807b9e..a1b15f44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -142,13 +142,18 @@
             STATE_IDLE,
             STATE_AUTHENTICATING_ANIMATING_IN,
             STATE_AUTHENTICATING,
-            STATE_PENDING_CONFIRMATION,
             STATE_AUTHENTICATED ->
                 if (isSideFps) {
                     R.string.security_settings_sfps_enroll_find_sensor_message
                 } else {
                     R.string.fingerprint_dialog_touch_sensor
                 }
+            STATE_PENDING_CONFIRMATION ->
+                if (isSideFps) {
+                    R.string.security_settings_sfps_enroll_find_sensor_message
+                } else {
+                    R.string.fingerprint_dialog_authenticated_confirmation
+                }
             STATE_ERROR,
             STATE_HELP -> R.string.biometric_dialog_try_again
             else -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index a5e846a..53dc0e3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.dagger
 
 import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FacePropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
 import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -53,6 +55,10 @@
 
     @Binds
     @SysUISingleton
+    fun faceSensors(impl: FacePropertyRepositoryImpl): FacePropertyRepository
+
+    @Binds
+    @SysUISingleton
     fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
new file mode 100644
index 0000000..d2cb849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.biometrics.data.repository
+
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** A repository for the global state of Face sensor. */
+interface FacePropertyRepository {
+    /** Face sensor information, null if it is not available. */
+    val sensorInfo: Flow<FaceSensorInfo?>
+}
+
+/** Describes a biometric sensor */
+data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
+
+private const val TAG = "FaceSensorPropertyRepositoryImpl"
+
+@SysUISingleton
+class FacePropertyRepositoryImpl
+@Inject
+constructor(@Application private val applicationScope: CoroutineScope, faceManager: FaceManager?) :
+    FacePropertyRepository {
+
+    private val sensorProps: Flow<List<FaceSensorPropertiesInternal>> =
+        faceManager?.let {
+            ConflatedCallbackFlow.conflatedCallbackFlow {
+                    val callback =
+                        object : IFaceAuthenticatorsRegisteredCallback.Stub() {
+                            override fun onAllAuthenticatorsRegistered(
+                                sensors: List<FaceSensorPropertiesInternal>
+                            ) {
+                                trySendWithFailureLogging(
+                                    sensors,
+                                    TAG,
+                                    "onAllAuthenticatorsRegistered"
+                                )
+                            }
+                        }
+                    it.addAuthenticatorsRegisteredCallback(callback)
+                    awaitClose {}
+                }
+                .shareIn(applicationScope, SharingStarted.Eagerly)
+        }
+            ?: flowOf(emptyList())
+
+    override val sensorInfo: Flow<FaceSensorInfo?> =
+        sensorProps
+            .map { it.firstOrNull() }
+            .map { it?.let { FaceSensorInfo(it.sensorId, it.sensorStrength.toSensorStrength()) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index efc92ad..daff5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -22,6 +22,7 @@
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -106,7 +107,7 @@
 
     private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
         _sensorId.value = prop.sensorId
-        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
+        _strength.value = prop.sensorStrength.toSensorStrength()
         _sensorType.value = sensorTypeIntToObject(prop.sensorType)
         _sensorLocations.value =
             prop.allLocations.associateBy { sensorLocationInternal ->
@@ -119,15 +120,6 @@
     }
 }
 
-private fun sensorStrengthIntToObject(value: Int): SensorStrength {
-    return when (value) {
-        0 -> SensorStrength.CONVENIENCE
-        1 -> SensorStrength.WEAK
-        2 -> SensorStrength.STRONG
-        else -> throw IllegalArgumentException("Invalid SensorStrength value: $value")
-    }
-}
-
 private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
     return when (value) {
         0 -> FingerprintSensorType.UNKNOWN
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 2982d0b..30e865e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -18,9 +18,18 @@
 
 import android.hardware.biometrics.SensorProperties
 
-/** Fingerprint sensor security strength. Represents [SensorProperties.Strength]. */
+/** Sensor security strength. Represents [SensorProperties.Strength]. */
 enum class SensorStrength {
     CONVENIENCE,
     WEAK,
     STRONG,
 }
+
+/** Convert [this] to corresponding [SensorStrength] */
+fun Int.toSensorStrength(): SensorStrength =
+    when (this) {
+        0 -> SensorStrength.CONVENIENCE
+        1 -> SensorStrength.WEAK
+        2 -> SensorStrength.STRONG
+        else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 3eb58bb..ec76f43 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -38,6 +38,7 @@
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
 import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.ui.CanUseIconPredicate
 import com.android.systemui.controls.ui.RenderInfo
 
 private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
@@ -51,7 +52,8 @@
  * @property elevation elevation of each control view
  */
 class ControlAdapter(
-    private val elevation: Float
+    private val elevation: Float,
+    private val currentUserId: Int,
 ) : RecyclerView.Adapter<Holder>() {
 
     companion object {
@@ -107,7 +109,8 @@
                         background = parent.context.getDrawable(
                                 R.drawable.control_background_ripple)
                     },
-                    model?.moveHelper // Indicates that position information is needed
+                    currentUserId,
+                    model?.moveHelper, // Indicates that position information is needed
                 ) { id, favorite ->
                     model?.changeFavoriteStatus(id, favorite)
                 }
@@ -212,8 +215,9 @@
  */
 internal class ControlHolder(
     view: View,
+    currentUserId: Int,
     val moveHelper: ControlsModel.MoveHelper?,
-    val favoriteCallback: ModelFavoriteChanger
+    val favoriteCallback: ModelFavoriteChanger,
 ) : Holder(view) {
     private val favoriteStateDescription =
         itemView.context.getString(R.string.accessibility_control_favorite)
@@ -228,6 +232,7 @@
         visibility = View.VISIBLE
     }
 
+    private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
     private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
         this::stateDescription,
         this::getLayoutPosition,
@@ -287,7 +292,9 @@
         val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
 
         icon.imageTintList = null
-        ci.customIcon?.let {
+        ci.customIcon
+                ?.takeIf(canUseIconPredicate)
+                ?.let {
             icon.setImageIcon(it)
         } ?: run {
             icon.setImageDrawable(ri.icon)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 8e41974..b387e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -250,7 +250,7 @@
         val elevation = resources.getFloat(R.dimen.control_card_elevation)
         val recyclerView = requireViewById<RecyclerView>(R.id.list)
         recyclerView.alpha = 0.0f
-        val adapter = ControlAdapter(elevation).apply {
+        val adapter = ControlAdapter(elevation, userTracker.userId).apply {
             registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
                 var hasAnimated = false
                 override fun onChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index d3aa449..59fa7f5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -197,7 +197,7 @@
                 }
 
                 executor.execute {
-                    structurePager.adapter = StructureAdapter(listOfStructures)
+                    structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId)
                     structurePager.setCurrentItem(structureIndex)
                     if (error) {
                         statusText.text = resources.getString(R.string.controls_favorite_load_error,
@@ -243,7 +243,7 @@
         structurePager.alpha = 0.0f
         pageIndicator.alpha = 0.0f
         structurePager.apply {
-            adapter = StructureAdapter(emptyList())
+            adapter = StructureAdapter(emptyList(), userTracker.userId)
             registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
                 override fun onPageSelected(position: Int) {
                     super.onPageSelected(position)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
index 747bcbe..5977d37 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
@@ -24,13 +24,15 @@
 import com.android.systemui.R
 
 class StructureAdapter(
-    private val models: List<StructureContainer>
+    private val models: List<StructureContainer>,
+    private val currentUserId: Int,
 ) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() {
 
     override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder {
         val layoutInflater = LayoutInflater.from(parent.context)
         return StructureHolder(
-            layoutInflater.inflate(R.layout.controls_structure_page, parent, false)
+            layoutInflater.inflate(R.layout.controls_structure_page, parent, false),
+            currentUserId,
         )
     }
 
@@ -40,7 +42,8 @@
         holder.bind(models[index].model)
     }
 
-    class StructureHolder(view: View) : RecyclerView.ViewHolder(view) {
+    class StructureHolder(view: View, currentUserId: Int) :
+            RecyclerView.ViewHolder(view) {
 
         private val recyclerView: RecyclerView
         private val controlAdapter: ControlAdapter
@@ -48,7 +51,7 @@
         init {
             recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll)
             val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation)
-            controlAdapter = ControlAdapter(elevation)
+            controlAdapter = ControlAdapter(elevation, currentUserId)
             setUpRecyclerView()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt
new file mode 100644
index 0000000..61c2123
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.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.controls.ui
+
+import android.content.ContentProvider
+import android.graphics.drawable.Icon
+
+class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean {
+
+    override fun invoke(icon: Icon): Boolean =
+        if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+            ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId
+        } else {
+            true
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index e6361f4..c04bc87 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -68,7 +68,8 @@
     val bgExecutor: DelayableExecutor,
     val controlActionCoordinator: ControlActionCoordinator,
     val controlsMetricsLogger: ControlsMetricsLogger,
-    val uid: Int
+    val uid: Int,
+    val currentUserId: Int,
 ) {
 
     companion object {
@@ -85,29 +86,9 @@
         private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled)
         const val MIN_LEVEL = 0
         const val MAX_LEVEL = 10000
-
-        fun findBehaviorClass(
-            status: Int,
-            template: ControlTemplate,
-            deviceType: Int
-        ): Supplier<out Behavior> {
-            return when {
-                status != Control.STATUS_OK -> Supplier { StatusBehavior() }
-                template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
-                template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }
-
-                // Required for legacy support, or where cameras do not use the new template
-                deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
-                template is ToggleTemplate -> Supplier { ToggleBehavior() }
-                template is StatelessTemplate -> Supplier { TouchBehavior() }
-                template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
-                template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
-                template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
-                else -> Supplier { DefaultBehavior() }
-            }
-        }
     }
 
+    private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
     private val toggleBackgroundIntensity: Float = layout.context.resources
             .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
     private var stateAnimator: ValueAnimator? = null
@@ -147,6 +128,27 @@
         status.setSelected(true)
     }
 
+    fun findBehaviorClass(
+            status: Int,
+            template: ControlTemplate,
+            deviceType: Int
+    ): Supplier<out Behavior> {
+        return when {
+            status != Control.STATUS_OK -> Supplier { StatusBehavior() }
+            template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
+            template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) }
+
+            // Required for legacy support, or where cameras do not use the new template
+            deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
+            template is ToggleTemplate -> Supplier { ToggleBehavior() }
+            template is StatelessTemplate -> Supplier { TouchBehavior() }
+            template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
+            template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
+            template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
+            else -> Supplier { DefaultBehavior() }
+        }
+    }
+
     fun bindData(cws: ControlWithState, isLocked: Boolean) {
         // If an interaction is in progress, the update may visually interfere with the action the
         // action the user wants to make. Don't apply the update, and instead assume a new update
@@ -473,7 +475,9 @@
 
         status.setTextColor(color)
 
-        control?.getCustomIcon()?.let {
+        control?.customIcon
+                ?.takeIf(canUseIconPredicate)
+                ?.let {
             icon.setImageIcon(it)
             icon.imageTintList = it.tintList
         } ?: run {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 776b336e..631ed3c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -690,7 +690,8 @@
                     bgExecutor,
                     controlActionCoordinator,
                     controlsMetricsLogger,
-                    selected.uid
+                    selected.uid,
+                    controlsController.get().currentUserId,
                 )
                 cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
                 controlViewsById.put(key, cvh)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index a7dc09b..39d6970 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -63,7 +63,7 @@
             // interactions (touch, range)
             subBehavior = cvh.bindBehavior(
                 subBehavior,
-                ControlViewHolder.findBehaviorClass(
+                cvh.findBehaviorClass(
                     control.status,
                     subTemplate,
                     control.deviceType
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
index c2168aa..0b57e79 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
@@ -33,7 +33,7 @@
  * Supports display of static images on the background of the tile. When marked active, the title
  * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only.
  */
-class ThumbnailBehavior : Behavior {
+class ThumbnailBehavior(currentUserId: Int) : Behavior {
     lateinit var template: ThumbnailTemplate
     lateinit var control: Control
     lateinit var cvh: ControlViewHolder
@@ -42,6 +42,7 @@
     private var shadowRadius: Float = 0f
     private var shadowColor: Int = 0
 
+    private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
     private val enabled: Boolean
         get() = template.isActive()
 
@@ -80,11 +81,16 @@
             cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor)
 
             cvh.bgExecutor.execute {
-                val drawable = template.getThumbnail().loadDrawable(cvh.context)
+                val drawable = template.thumbnail
+                        ?.takeIf(canUseIconPredicate)
+                        ?.loadDrawable(cvh.context)
                 cvh.uiExecutor.execute {
                     val radius = cvh.context.getResources()
                         .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
-                    clipLayer.setDrawable(CornerDrawable(drawable, radius))
+                    // TODO(b/290037843): Add a placeholder
+                    drawable?.let {
+                        clipLayer.drawable = CornerDrawable(it, radius)
+                    }
                     clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources
                         .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY))
                     cvh.applyRenderInfo(enabled, colorOffset)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index bf1e75b..6fd3e21 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,6 +26,8 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -58,6 +60,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
@@ -68,6 +71,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -120,6 +124,7 @@
     fun cancel()
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class DeviceEntryFaceAuthRepositoryImpl
 @Inject
@@ -143,7 +148,8 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val featureFlags: FeatureFlags,
+    featureFlags: FeatureFlags,
+    facePropertyRepository: FacePropertyRepository,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -163,6 +169,13 @@
     override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
 
+    private val isFaceBiometricsAllowed: Flow<Boolean> =
+        facePropertyRepository.sensorInfo.flatMapLatest {
+            if (it?.strength == SensorStrength.STRONG)
+                biometricSettingsRepository.isStrongBiometricAllowed
+            else biometricSettingsRepository.isNonStrongBiometricAllowed
+        }
+
     private val _isLockedOut = MutableStateFlow(false)
     override val isLockedOut: StateFlow<Boolean> = _isLockedOut
 
@@ -274,10 +287,8 @@
                 canFaceAuthOrDetectRun(faceDetectLog),
                 logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed
-                        .isFalse()
-                        .or(trustRepository.isCurrentUserTrusted),
-                    "nonStrongBiometricIsNotAllowedOrCurrentUserIsTrusted",
+                    isFaceBiometricsAllowed.isFalse().or(trustRepository.isCurrentUserTrusted),
+                    "biometricIsNotAllowedOrCurrentUserIsTrusted",
                     faceDetectLog
                 ),
                 // We don't want to run face detect if fingerprint can be used to unlock the device
@@ -369,20 +380,11 @@
                 canFaceAuthOrDetectRun(faceAuthLog),
                 logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                 logAndObserve(
-                    deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
-                    "fpIsNotLockedOut",
-                    faceAuthLog
-                ),
-                logAndObserve(
                     trustRepository.isCurrentUserTrusted.isFalse(),
                     "currentUserIsNotTrusted",
                     faceAuthLog
                 ),
-                logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed,
-                    "nonStrongBiometricIsAllowed",
-                    faceAuthLog
-                ),
+                logAndObserve(isFaceBiometricsAllowed, "isFaceBiometricsAllowed", faceAuthLog),
                 logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
             )
             .reduce(::and)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8f884d24..42164c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -85,7 +85,7 @@
 
     public override fun onCreate(bundle: Bundle?) {
         lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
-        component = componentFactory.create(activity = this, view = this, resultHandler = this)
+        component = componentFactory.create(view = this, resultHandler = this)
         component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
         // Create a separate configuration controller for this activity as the configuration
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 11538fa..e0869ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -161,7 +161,6 @@
     interface Factory {
         /** Create a factory to inject the activity into the graph */
         fun create(
-            @BindsInstance activity: MediaProjectionAppSelectorActivity,
             @BindsInstance view: MediaProjectionAppSelectorView,
             @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
         ): MediaProjectionAppSelectorComponent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index a6ad4b2..a86937f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -89,6 +89,22 @@
             utils.authenticationRepository.setLockscreenEnabled(false)
 
             val isUnlocked by collectLastValue(underTest.isUnlocked)
+            // Toggle isUnlocked, twice.
+            //
+            // This is done because the underTest.isUnlocked flow doesn't receive values from
+            // just changing the state above; the actual isUnlocked state needs to change to
+            // cause the logic under test to "pick up" the current state again.
+            //
+            // It is done twice to make sure that we don't actually change the isUnlocked
+            // state from what it originally was.
+            utils.authenticationRepository.setUnlocked(
+                !utils.authenticationRepository.isUnlocked.value
+            )
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(
+                !utils.authenticationRepository.isUnlocked.value
+            )
+            runCurrent()
             assertThat(isUnlocked).isTrue()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 9cabd35..5766f1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -18,6 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import androidx.test.filters.RequiresDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.ShadeExpansionStateManager
 import org.junit.Assert
@@ -30,6 +31,7 @@
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.junit.MockitoJUnit
 
+@RequiresDevice
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
new file mode 100644
index 0000000..fcc4040
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.biometrics.data.repository
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FacePropertyRepositoryImplTest : SysuiTestCase() {
+    @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    private lateinit var underTest: FacePropertyRepository
+    private lateinit var testScope: TestScope
+
+    @Captor private lateinit var callback: ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
+    @Mock private lateinit var faceManager: FaceManager
+    @Before
+    fun setup() {
+        testScope = TestScope()
+        underTest = createRepository(faceManager)
+    }
+
+    private fun createRepository(manager: FaceManager? = faceManager) =
+        FacePropertyRepositoryImpl(testScope.backgroundScope, manager)
+
+    @Test
+    fun whenFaceManagerIsNotPresentIsNull() =
+        testScope.runTest {
+            underTest = createRepository(null)
+            val sensor = collectLastValue(underTest.sensorInfo)
+
+            assertThat(sensor()).isNull()
+        }
+
+    @Test
+    fun providesTheValuePassedToTheAuthenticatorsRegisteredCallback() {
+        testScope.runTest {
+            val sensor by collectLastValue(underTest.sensorInfo)
+            runCurrent()
+            verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+
+            callback.value.onAllAuthenticatorsRegistered(
+                listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+            )
+
+            assertThat(sensor).isEqualTo(FaceSensorInfo(1, SensorStrength.STRONG))
+        }
+    }
+
+    private fun createSensorProperties(id: Int, strength: Int) =
+        FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
new file mode 100644
index 0000000..bfdb923
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.controls.ui
+
+import android.content.ContentProvider
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CanUseIconPredicateTest : SysuiTestCase() {
+
+    private companion object {
+        const val USER_ID_1 = 1
+        const val USER_ID_2 = 2
+    }
+
+    val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1)
+
+    @Test
+    fun testReturnsFalseForDifferentUser() {
+        val user2Icon =
+            Icon.createWithContentUri(
+                ContentProvider.createContentUriForUser(
+                    Uri.parse("content://test"),
+                    UserHandle.of(USER_ID_2)
+                )
+            )
+
+        assertThat(underTest.invoke(user2Icon)).isFalse()
+    }
+
+    @Test
+    fun testReturnsTrueForCorrectUser() {
+        val user1Icon =
+            Icon.createWithContentUri(
+                ContentProvider.createContentUriForUser(
+                    Uri.parse("content://test"),
+                    UserHandle.of(USER_ID_1)
+                )
+            )
+
+        assertThat(underTest.invoke(user1Icon)).isTrue()
+    }
+
+    @Test
+    fun testReturnsTrueForUriWithoutUser() {
+        val uriIcon = Icon.createWithContentUri(Uri.parse("content://test"))
+
+        assertThat(underTest.invoke(uriIcon)).isTrue()
+    }
+
+    @Test
+    fun testReturnsTrueForNonUriIcon() {
+        val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+
+        assertThat(underTest.invoke(bitmapIcon)).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index d3c465d..42f28c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -66,7 +66,8 @@
                     FakeExecutor(clock),
                     mock(ControlActionCoordinator::class.java),
                     mock(ControlsMetricsLogger::class.java),
-                    uid = 100
+                    uid = 100,
+                    0,
             )
 
             val cws = ControlWithState(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index b3f8000..01a6c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -40,6 +40,9 @@
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FaceSensorInfo
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
@@ -151,6 +154,7 @@
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
 
     private var wasAuthCancelled = false
     private var wasDetectCancelled = false
@@ -224,6 +228,7 @@
                     repository = keyguardTransitionRepository,
                 )
                 .keyguardTransitionInteractor
+        fakeFacePropertyRepository = FakeFacePropertyRepository()
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
@@ -245,6 +250,7 @@
             faceAuthBuffer,
             keyguardTransitionInteractor,
             featureFlags,
+            fakeFacePropertyRepository,
             dumpManager,
         )
     }
@@ -591,6 +597,17 @@
         }
 
     @Test
+    fun authenticateDoesNotRunWhenStrongBiometricIsNotAllowedAndFaceSensorIsStrong() =
+        testScope.runTest {
+            fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
+            runCurrent()
+
+            testGatingCheckForFaceAuth(isFaceStrong = true) {
+                biometricSettingsRepository.setIsStrongBiometricAllowed(false)
+            }
+        }
+
+    @Test
     fun authenticateDoesNotRunWhenSecureCameraIsActive() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -923,6 +940,19 @@
         }
 
     @Test
+    fun detectDoesNotRunWhenStrongBiometricIsAllowedAndFaceAuthSensorStrengthIsStrong() =
+        testScope.runTest {
+            fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
+            runCurrent()
+
+            testGatingCheckForDetect(isFaceStrong = true) {
+                biometricSettingsRepository.setIsStrongBiometricAllowed(true)
+                // this shouldn't matter as face is set as a strong sensor
+                biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+            }
+        }
+
+    @Test
     fun detectDoesNotRunIfUdfpsIsRunning() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -1013,9 +1043,12 @@
             faceAuthenticateIsCalled()
         }
 
-    private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
+    private suspend fun TestScope.testGatingCheckForFaceAuth(
+        isFaceStrong: Boolean = false,
+        gatingCheckModifier: () -> Unit
+    ) {
         initCollectors()
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
 
         gatingCheckModifier()
         runCurrent()
@@ -1024,7 +1057,7 @@
         assertThat(underTest.canRunFaceAuth.value).isFalse()
 
         // flip the gating check back on.
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(isFaceStrong)
 
         triggerFaceAuth(false)
 
@@ -1043,12 +1076,19 @@
         faceAuthenticateIsNotCalled()
     }
 
-    private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
+    private suspend fun TestScope.testGatingCheckForDetect(
+        isFaceStrong: Boolean = false,
+        gatingCheckModifier: () -> Unit
+    ) {
         initCollectors()
         allPreconditionsToRunFaceAuthAreTrue()
 
-        // This will stop face auth from running but is required to be false for detect.
-        biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+        if (isFaceStrong) {
+            biometricSettingsRepository.setStrongBiometricAllowed(false)
+        } else {
+            // This will stop face auth from running but is required to be false for detect.
+            biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+        }
         runCurrent()
 
         assertThat(canFaceAuthRun()).isFalse()
@@ -1083,7 +1123,9 @@
         cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
     }
 
-    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+        isFaceStrong: Boolean = false
+    ) {
         verify(faceManager, atLeastOnce())
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
         biometricSettingsRepository.setFaceEnrolled(true)
@@ -1098,7 +1140,11 @@
                 WakeSleepReason.OTHER
             )
         )
-        biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+        if (isFaceStrong) {
+            biometricSettingsRepository.setStrongBiometricAllowed(true)
+        } else {
+            biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+        }
         biometricSettingsRepository.setIsUserInLockdown(false)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 8c9ed5b..8636dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -342,12 +342,13 @@
         }
 
     @Test
-    fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest {
-        underTest.start()
+    fun faceUnlockIsDisabledWhenFpIsLockedOut() =
+        testScope.runTest {
+            underTest.start()
 
-        fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
-        runCurrent()
+            fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
 
-        assertThat(faceAuthRepository.wasDisabled).isTrue()
-    }
+            assertThat(faceAuthRepository.wasDisabled).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
new file mode 100644
index 0000000..2ef1be7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.biometrics.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFacePropertyRepository : FacePropertyRepository {
+    private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
+    override val sensorInfo: Flow<FaceSensorInfo?>
+        get() = faceSensorInfo
+
+    fun setSensorInfo(value: FaceSensorInfo?) {
+        faceSensorInfo.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 4aaf347..8c98aea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -59,7 +59,6 @@
     private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
     override val authenticationFlags: Flow<AuthenticationFlags>
         get() = _authFlags
-
     fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
         _isFingerprintEnrolled.value = isFingerprintEnrolled
     }
@@ -110,4 +109,8 @@
     fun setIsNonStrongBiometricAllowed(value: Boolean) {
         _isNonStrongBiometricAllowed.value = value
     }
+
+    fun setIsStrongBiometricAllowed(value: Boolean) {
+        _isStrongBiometricAllowed.value = value
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index fb26f42..70aff05 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2401,6 +2401,21 @@
         }
     }
 
+    @Override
+    public void requestHideFillUiWhenDestroyed(AutofillId id) {
+        synchronized (mLock) {
+            // NOTE: We allow this call in a destroyed state as the UI is
+            // asked to go away after we get destroyed, so let it do that.
+            try {
+                mClient.requestHideFillUiWhenDestroyed(this.id, id);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error requesting to hide fill UI", e);
+            }
+
+            mInlineSessionController.hideInlineSuggestionsUiLocked(id);
+        }
+    }
+
     // AutoFillUiCallback
     @Override
     public void cancelSession() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index f92d38d..d479dfb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -95,6 +95,7 @@
         void requestShowFillUi(AutofillId id, int width, int height,
                 IAutofillWindowPresenter presenter);
         void requestHideFillUi(AutofillId id);
+        void requestHideFillUiWhenDestroyed(AutofillId id);
         void startIntentSenderAndFinishSession(IntentSender intentSender);
         void startIntentSender(IntentSender intentSender, Intent intent);
         void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
@@ -289,6 +290,13 @@
                 }
 
                 @Override
+                public void requestHideFillUiWhenDestroyed() {
+                    if (mCallback != null) {
+                        mCallback.requestHideFillUiWhenDestroyed(focusedId);
+                    }
+                }
+
+                @Override
                 public void startIntentSender(IntentSender intentSender) {
                     if (mCallback != null) {
                         mCallback.startIntentSenderAndFinishSession(intentSender);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index cdfe7bb..cdd9ef4 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -91,6 +91,7 @@
         void requestShowFillUi(int width, int height,
                 IAutofillWindowPresenter windowPresenter);
         void requestHideFillUi();
+        void requestHideFillUiWhenDestroyed();
         void startIntentSender(IntentSender intentSender);
         void dispatchUnhandledKey(KeyEvent keyEvent);
         void cancelSession();
@@ -482,7 +483,7 @@
         }
         mCallback.onDestroy();
         if (notifyClient) {
-            mCallback.requestHideFillUi();
+            mCallback.requestHideFillUiWhenDestroyed();
         }
         mDestroyed = true;
     }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c25b253..e5965ef 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
+import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -475,7 +478,7 @@
     private static final int DEFAULT_REFRESH_RATE_IN_HBM = 0;
     private static final int DEFAULT_LOW_REFRESH_RATE = 60;
     private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
-    private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
+    private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
 
     private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
     private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
@@ -691,9 +694,14 @@
      * prevent flicker, we only support higher refresh rates if the display brightness is above a
      * threshold. For example, no higher refresh rate if display brightness <= disp0 && ambient
      * brightness <= amb0 || display brightness <= disp1 && ambient brightness <= amb1
+     *
+     * Brightness thresholds are paired with lux thresholds - they both have to be met.
+     *
+     * A negative brightness or lux value means that only one threshold should be used - e.g. if
+     * the brightness value is negative, only the lux threshold is applied.
      */
-    private int[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
-    private int[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
     /**
      * The display uses different gamma curves for different refresh rates. It's hard for panel
@@ -705,9 +713,14 @@
      * have lower display brightness requirements for the flickering is visible. For example, fixed
      * refresh rate if display brightness >= disp0 && ambient brightness >= amb0 || display
      * brightness >= disp1 && ambient brightness >= amb1
+     *
+     * Brightness thresholds are paired with lux thresholds - they both have to be met.
+     *
+     * A negative brightness or lux value means that only one threshold should be used - e.g. if
+     * the brightness value is negative, only the lux threshold is applied.
      */
-    private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
-    private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
     private final HashMap<String, ThermalBrightnessThrottlingData>
             mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
@@ -1493,36 +1506,44 @@
     /**
      * @return An array of lower display brightness thresholds. This, in combination with lower
      * ambient brightness thresholds help define buckets in which the refresh rate switching is not
-     * allowed
+     * allowed.
+     *
+     * A negative threshold value means that only the lux threshold is applied.
      */
-    public int[] getLowDisplayBrightnessThresholds() {
+    public float[] getLowDisplayBrightnessThresholds() {
         return mLowDisplayBrightnessThresholds;
     }
 
     /**
      * @return An array of lower ambient brightness thresholds. This, in combination with lower
      * display brightness thresholds help define buckets in which the refresh rate switching is not
-     * allowed
+     * allowed.
+     *
+     * A negative threshold value means that only the display brightness threshold is applied.
      */
-    public int[] getLowAmbientBrightnessThresholds() {
+    public float[] getLowAmbientBrightnessThresholds() {
         return mLowAmbientBrightnessThresholds;
     }
 
     /**
      * @return An array of high display brightness thresholds. This, in combination with high
      * ambient brightness thresholds help define buckets in which the refresh rate switching is not
-     * allowed
+     * allowed.
+     *
+     * A negative threshold value means that only the lux threshold is applied.
      */
-    public int[] getHighDisplayBrightnessThresholds() {
+    public float[] getHighDisplayBrightnessThresholds() {
         return mHighDisplayBrightnessThresholds;
     }
 
     /**
      * @return An array of high ambient brightness thresholds. This, in combination with high
      * display brightness thresholds help define buckets in which the refresh rate switching is not
-     * allowed
+     * allowed.
+     *
+     * A negative threshold value means that only the display brightness threshold is applied.
      */
-    public int[] getHighAmbientBrightnessThresholds() {
+    public float[] getHighAmbientBrightnessThresholds() {
         return mHighAmbientBrightnessThresholds;
     }
 
@@ -2144,34 +2165,46 @@
      */
     private void loadLowerBrightnessThresholds(BlockingZoneConfig lowerBlockingZoneConfig) {
         if (lowerBlockingZoneConfig == null) {
-            mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] lowDisplayBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_brightnessThresholdsOfPeakRefreshRate);
-            mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] lowAmbientBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_ambientThresholdsOfPeakRefreshRate);
-            if (mLowDisplayBrightnessThresholds == null || mLowAmbientBrightnessThresholds == null
-                    || mLowDisplayBrightnessThresholds.length
-                    != mLowAmbientBrightnessThresholds.length) {
+            if (lowDisplayBrightnessThresholdsInt == null
+                    || lowAmbientBrightnessThresholdsInt == null
+                    || lowDisplayBrightnessThresholdsInt.length
+                    != lowAmbientBrightnessThresholdsInt.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                     + "brightness threshold array have different length: "
-                    + "mLowDisplayBrightnessThresholds="
-                    + Arrays.toString(mLowDisplayBrightnessThresholds)
-                    + ", mLowAmbientBrightnessThresholds="
-                    + Arrays.toString(mLowAmbientBrightnessThresholds));
+                    + "lowDisplayBrightnessThresholdsInt="
+                    + Arrays.toString(lowDisplayBrightnessThresholdsInt)
+                    + ", lowAmbientBrightnessThresholdsInt="
+                    + Arrays.toString(lowAmbientBrightnessThresholdsInt));
             }
+
+            mLowDisplayBrightnessThresholds =
+                    displayBrightnessThresholdsIntToFloat(lowDisplayBrightnessThresholdsInt);
+            mLowAmbientBrightnessThresholds =
+                    ambientBrightnessThresholdsIntToFloat(lowAmbientBrightnessThresholdsInt);
         } else {
             List<DisplayBrightnessPoint> lowerThresholdDisplayBrightnessPoints =
                     lowerBlockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
             int size = lowerThresholdDisplayBrightnessPoints.size();
-            mLowDisplayBrightnessThresholds = new int[size];
-            mLowAmbientBrightnessThresholds = new int[size];
+            mLowDisplayBrightnessThresholds = new float[size];
+            mLowAmbientBrightnessThresholds = new float[size];
             for (int i = 0; i < size; i++) {
-                // We are explicitly casting this value to an integer to be able to reuse the
-                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
-                // will have the negligible and unnoticeable impact on the loaded thresholds.
-                mLowDisplayBrightnessThresholds[i] = (int) lowerThresholdDisplayBrightnessPoints
-                    .get(i).getNits().floatValue();
+                float thresholdNits = lowerThresholdDisplayBrightnessPoints
+                        .get(i).getNits().floatValue();
+                if (thresholdNits < 0) {
+                    // A negative value means that there's no threshold
+                    mLowDisplayBrightnessThresholds[i] = thresholdNits;
+                } else {
+                    float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits);
+                    mLowDisplayBrightnessThresholds[i] =
+                            mBacklightToBrightnessSpline.interpolate(thresholdBacklight);
+                }
+
                 mLowAmbientBrightnessThresholds[i] = lowerThresholdDisplayBrightnessPoints
-                    .get(i).getLux().intValue();
+                    .get(i).getLux().floatValue();
             }
         }
     }
@@ -2183,34 +2216,46 @@
      */
     private void loadHigherBrightnessThresholds(BlockingZoneConfig blockingZoneConfig) {
         if (blockingZoneConfig == null) {
-            mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] highDisplayBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-            mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] highAmbientBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
-            if (mHighAmbientBrightnessThresholds == null || mHighDisplayBrightnessThresholds == null
-                    || mHighAmbientBrightnessThresholds.length
-                    != mHighDisplayBrightnessThresholds.length) {
+            if (highDisplayBrightnessThresholdsInt == null
+                    || highAmbientBrightnessThresholdsInt == null
+                    || highDisplayBrightnessThresholdsInt.length
+                    != highAmbientBrightnessThresholdsInt.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
                     + "brightness threshold array have different length: "
-                    + "mHighDisplayBrightnessThresholds="
-                    + Arrays.toString(mHighDisplayBrightnessThresholds)
-                    + ", mHighAmbientBrightnessThresholds="
-                    + Arrays.toString(mHighAmbientBrightnessThresholds));
+                    + "highDisplayBrightnessThresholdsInt="
+                    + Arrays.toString(highDisplayBrightnessThresholdsInt)
+                    + ", highAmbientBrightnessThresholdsInt="
+                    + Arrays.toString(highAmbientBrightnessThresholdsInt));
             }
+
+            mHighDisplayBrightnessThresholds =
+                    displayBrightnessThresholdsIntToFloat(highDisplayBrightnessThresholdsInt);
+            mHighAmbientBrightnessThresholds =
+                    ambientBrightnessThresholdsIntToFloat(highAmbientBrightnessThresholdsInt);
         } else {
             List<DisplayBrightnessPoint> higherThresholdDisplayBrightnessPoints =
                     blockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
             int size = higherThresholdDisplayBrightnessPoints.size();
-            mHighDisplayBrightnessThresholds = new int[size];
-            mHighAmbientBrightnessThresholds = new int[size];
+            mHighDisplayBrightnessThresholds = new float[size];
+            mHighAmbientBrightnessThresholds = new float[size];
             for (int i = 0; i < size; i++) {
-                // We are explicitly casting this value to an integer to be able to reuse the
-                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
-                // will have the negligible and unnoticeable impact on the loaded thresholds.
-                mHighDisplayBrightnessThresholds[i] = (int) higherThresholdDisplayBrightnessPoints
-                    .get(i).getNits().floatValue();
+                float thresholdNits = higherThresholdDisplayBrightnessPoints
+                        .get(i).getNits().floatValue();
+                if (thresholdNits < 0) {
+                    // A negative value means that there's no threshold
+                    mHighDisplayBrightnessThresholds[i] = thresholdNits;
+                } else {
+                    float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits);
+                    mHighDisplayBrightnessThresholds[i] =
+                            mBacklightToBrightnessSpline.interpolate(thresholdBacklight);
+                }
+
                 mHighAmbientBrightnessThresholds[i] = higherThresholdDisplayBrightnessPoints
-                    .get(i).getLux().intValue();
+                    .get(i).getLux().floatValue();
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1dfe6b2..59b8871 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -724,7 +724,9 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_animateScreenLights);
 
@@ -1260,7 +1262,7 @@
             float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
             if (userNits >= 0) {
                 userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                if (Float.isNaN(userBrightness)) {
                     userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
                 }
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1d8b494..88fc1fb 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -611,7 +611,9 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = mInjector.isColorFadeEnabled();
+        mColorFadeEnabled = mInjector.isColorFadeEnabled()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
                 R.bool.config_animateScreenLights);
 
@@ -1081,7 +1083,7 @@
             float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
             if (userNits >= 0) {
                 userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                if (Float.isNaN(userBrightness)) {
                     userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
                 }
             }
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index dfb5f62..23ffe59 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -23,6 +23,8 @@
 import android.provider.DeviceConfigInterface;
 import android.util.Slog;
 
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -102,32 +104,64 @@
                 DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
     }
 
-    /** Return null if no such property or wrong format (not comma separated integers). */
+    /**
+     * Get the high ambient brightness thresholds for the configured refresh rate zone. The values
+     * are paired with brightness thresholds.
+     *
+     * A negative value means that only the display brightness threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
     @Nullable
-    public int[] getHighAmbientBrightnessThresholds() {
-        return getIntArrayProperty(DisplayManager.DeviceConfig
-                .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    public float[] getHighAmbientBrightnessThresholds() {
+        return DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS));
     }
 
-    /** Return null if no such property or wrong format (not comma separated integers). */
+    /**
+     * Get the high display brightness thresholds for the configured refresh rate zone. The values
+     * are paired with lux thresholds.
+     *
+     * A negative value means that only the ambient threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
     @Nullable
-    public int[] getHighDisplayBrightnessThresholds() {
-        return getIntArrayProperty(DisplayManager.DeviceConfig
-                .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    public float[] getHighDisplayBrightnessThresholds() {
+        return DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS));
     }
 
-    /** Return null if no such property or wrong format (not comma separated integers). */
+    /**
+     * Get the low display brightness thresholds for the configured refresh rate zone. The values
+     * are paired with lux thresholds.
+     *
+     * A negative value means that only the ambient threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
     @Nullable
-    public int[] getLowDisplayBrightnessThresholds() {
-        return getIntArrayProperty(DisplayManager.DeviceConfig
-                .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    public float[] getLowDisplayBrightnessThresholds() {
+        return DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS));
     }
 
-    /** Return null if no such property or wrong format (not comma separated integers). */
+    /**
+     * Get the low ambient brightness thresholds for the configured refresh rate zone. The values
+     * are paired with brightness thresholds.
+     *
+     * A negative value means that only the display brightness threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
     @Nullable
-    public int[] getLowAmbientBrightnessThresholds() {
-        return getIntArrayProperty(DisplayManager.DeviceConfig
-                .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    public float[] getLowAmbientBrightnessThresholds() {
+        return DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS));
     }
 
     /** add property change listener to DeviceConfig */
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 11e35ce..82755b6 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -18,7 +18,7 @@
 
 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
-import static android.os.PowerManager.BRIGHTNESS_INVALID;
+import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
 import android.annotation.IntegerRes;
 import android.annotation.NonNull;
@@ -42,6 +42,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -72,6 +73,7 @@
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
@@ -86,6 +88,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Callable;
+import java.util.function.Function;
 import java.util.function.IntSupplier;
 
 /**
@@ -895,7 +898,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED: {
-                    Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+                    Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj;
                     mBrightnessObserver.onDeviceConfigLowBrightnessThresholdsChanged(
                             thresholds.first, thresholds.second);
                     break;
@@ -909,7 +912,7 @@
                 }
 
                 case MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED: {
-                    Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+                    Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj;
 
                     mBrightnessObserver.onDeviceConfigHighBrightnessThresholdsChanged(
                             thresholds.first, thresholds.second);
@@ -1487,10 +1490,32 @@
     @VisibleForTesting
     public class BrightnessObserver implements DisplayManager.DisplayListener {
         private static final int LIGHT_SENSOR_RATE_MS = 250;
-        private int[] mLowDisplayBrightnessThresholds;
-        private int[] mLowAmbientBrightnessThresholds;
-        private int[] mHighDisplayBrightnessThresholds;
-        private int[] mHighAmbientBrightnessThresholds;
+
+        /**
+         * Brightness thresholds for the low zone. Paired with lux thresholds.
+         *
+         * A negative value means that only the lux threshold should be applied.
+         */
+        private float[] mLowDisplayBrightnessThresholds;
+        /**
+         * Lux thresholds for the low zone. Paired with brightness thresholds.
+         *
+         * A negative value means that only the display brightness threshold should be applied.
+         */
+        private float[] mLowAmbientBrightnessThresholds;
+
+        /**
+         * Brightness thresholds for the high zone. Paired with lux thresholds.
+         *
+         * A negative value means that only the lux threshold should be applied.
+         */
+        private float[] mHighDisplayBrightnessThresholds;
+        /**
+         * Lux thresholds for the high zone. Paired with brightness thresholds.
+         *
+         * A negative value means that only the display brightness threshold should be applied.
+         */
+        private float[] mHighAmbientBrightnessThresholds;
         // valid threshold if any item from the array >= 0
         private boolean mShouldObserveDisplayLowChange;
         private boolean mShouldObserveAmbientLowChange;
@@ -1508,7 +1533,8 @@
         // Take it as low brightness before valid sensor data comes
         private float mAmbientLux = -1.0f;
         private AmbientFilter mAmbientFilter;
-        private int mBrightness = -1;
+
+        private float mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
         private final Context mContext;
         private final Injector mInjector;
@@ -1546,22 +1572,22 @@
         }
 
         @VisibleForTesting
-        int[] getLowDisplayBrightnessThreshold() {
+        float[] getLowDisplayBrightnessThresholds() {
             return mLowDisplayBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getLowAmbientBrightnessThreshold() {
+        float[] getLowAmbientBrightnessThresholds() {
             return mLowAmbientBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getHighDisplayBrightnessThreshold() {
+        float[] getHighDisplayBrightnessThresholds() {
             return mHighDisplayBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getHighAmbientBrightnessThreshold() {
+        float[] getHighAmbientBrightnessThresholds() {
             return mHighAmbientBrightnessThresholds;
         }
 
@@ -1589,12 +1615,14 @@
                     () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptReadFromFeatureParams);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptReadFromFeatureParams);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1649,12 +1677,14 @@
                     () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptReadFromFeatureParams);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptReadFromFeatureParams);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1666,27 +1696,27 @@
             }
         }
 
-        private int[] loadBrightnessThresholds(
-                Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
-                Callable<int[]> loadFromDisplayDeviceConfigCallable,
+        private float[] loadBrightnessThresholds(
+                Callable<float[]> loadFromDeviceConfigDisplaySettingsCallable,
+                Callable<float[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) {
-            int[] brightnessThresholds = null;
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams,
+                Function<int[], float[]> conversion) {
+            float[] brightnessThresholds = null;
 
             if (attemptReadFromFeatureParams) {
                 try {
-                    brightnessThresholds =
-                        loadFromDeviceConfigDisplaySettingsCallable.call();
+                    brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
             if (brightnessThresholds == null) {
                 try {
-                    brightnessThresholds =
-                            (displayDeviceConfig == null) ? mContext.getResources().getIntArray(
-                                    brightnessThresholdOfFixedRefreshRateKey)
-                                    : loadFromDisplayDeviceConfigCallable.call();
+                    brightnessThresholds = displayDeviceConfig == null ? conversion.apply(
+                            mContext.getResources().getIntArray(
+                                    brightnessThresholdOfFixedRefreshRateKey)) :
+                            loadFromDisplayDeviceConfigCallable.call();
                 } catch (Exception e) {
                     Slog.e(TAG, "Unexpectedly failed to load display brightness threshold");
                     e.printStackTrace();
@@ -1695,32 +1725,15 @@
             return brightnessThresholds;
         }
 
-        /**
-         * @return the display brightness thresholds for the low brightness zones
-         */
-        @VisibleForTesting
-        int[] getLowDisplayBrightnessThresholds() {
-            return mLowDisplayBrightnessThresholds;
-        }
-
-        /**
-         * @return the ambient brightness thresholds for the low brightness zones
-         */
-        @VisibleForTesting
-        int[] getLowAmbientBrightnessThresholds() {
-            return mLowAmbientBrightnessThresholds;
-        }
-
         private void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
             mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
             // DeviceConfig is accessible after system ready.
-            int[] lowDisplayBrightnessThresholds =
+            float[] lowDisplayBrightnessThresholds =
                     mConfigParameterProvider.getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds =
+            float[] lowAmbientBrightnessThresholds =
                     mConfigParameterProvider.getLowAmbientBrightnessThresholds();
-
             if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
                     && lowDisplayBrightnessThresholds.length
                     == lowAmbientBrightnessThresholds.length) {
@@ -1728,11 +1741,10 @@
                 mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
             }
 
-            int[] highDisplayBrightnessThresholds =
+            float[] highDisplayBrightnessThresholds =
                     mConfigParameterProvider.getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds =
+            float[] highAmbientBrightnessThresholds =
                     mConfigParameterProvider.getHighAmbientBrightnessThresholds();
-
             if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
                     && highDisplayBrightnessThresholds.length
                     == highAmbientBrightnessThresholds.length) {
@@ -1787,8 +1799,8 @@
             }
         }
 
-        private void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
-                int[] ambientThresholds) {
+        private void onDeviceConfigLowBrightnessThresholdsChanged(float[] displayThresholds,
+                float[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
                 mLowDisplayBrightnessThresholds = displayThresholds;
@@ -1802,12 +1814,14 @@
                         () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                         R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
                 mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
                         () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                         R.array.config_ambientThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             }
             restartObserver();
         }
@@ -1831,8 +1845,8 @@
             }
         }
 
-        private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
-                int[] ambientThresholds) {
+        private void onDeviceConfigHighBrightnessThresholdsChanged(float[] displayThresholds,
+                float[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
                 mHighDisplayBrightnessThresholds = displayThresholds;
@@ -1846,12 +1860,14 @@
                         () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                         R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
                 mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
                         () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                         R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             }
             restartObserver();
         }
@@ -1886,11 +1902,11 @@
             pw.println("    mShouldObserveAmbientLowChange: " + mShouldObserveAmbientLowChange);
             pw.println("    mRefreshRateInLowZone: " + mRefreshRateInLowZone);
 
-            for (int d : mLowDisplayBrightnessThresholds) {
+            for (float d : mLowDisplayBrightnessThresholds) {
                 pw.println("    mDisplayLowBrightnessThreshold: " + d);
             }
 
-            for (int d : mLowAmbientBrightnessThresholds) {
+            for (float d : mLowAmbientBrightnessThresholds) {
                 pw.println("    mAmbientLowBrightnessThreshold: " + d);
             }
 
@@ -1898,11 +1914,11 @@
             pw.println("    mShouldObserveAmbientHighChange: " + mShouldObserveAmbientHighChange);
             pw.println("    mRefreshRateInHighZone: " + mRefreshRateInHighZone);
 
-            for (int d : mHighDisplayBrightnessThresholds) {
+            for (float d : mHighDisplayBrightnessThresholds) {
                 pw.println("    mDisplayHighBrightnessThresholds: " + d);
             }
 
-            for (int d : mHighAmbientBrightnessThresholds) {
+            for (float d : mHighAmbientBrightnessThresholds) {
                 pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
@@ -1931,9 +1947,9 @@
 
                 // We don't support multiple display blocking zones yet, so only handle
                 // brightness changes for the default display for now.
-                int brightness = getBrightness(displayId);
+                float brightness = getBrightness(displayId);
                 synchronized (mLock) {
-                    if (brightness != mBrightness) {
+                    if (!BrightnessSynchronizer.floatEquals(brightness, mBrightness)) {
                         mBrightness = brightness;
                         onBrightnessChangedLocked();
                     }
@@ -2012,8 +2028,8 @@
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
          */
-        private boolean hasValidThreshold(int[] a) {
-            for (int d: a) {
+        private boolean hasValidThreshold(float[] a) {
+            for (float d: a) {
                 if (d >= 0) {
                     return true;
                 }
@@ -2022,10 +2038,18 @@
             return false;
         }
 
-        private boolean isInsideLowZone(int brightness, float lux) {
+        /**
+         * Check if we're in the low zone where higher refresh rates aren't allowed to prevent
+         * flickering.
+         * @param brightness The brightness value or a negative value meaning that only the lux
+         *                   threshold should be applied
+         * @param lux The lux value. If negative, only the brightness threshold is applied
+         * @return True if we're in the low zone
+         */
+        private boolean isInsideLowZone(float brightness, float lux) {
             for (int i = 0; i < mLowDisplayBrightnessThresholds.length; i++) {
-                int disp = mLowDisplayBrightnessThresholds[i];
-                int ambi = mLowAmbientBrightnessThresholds[i];
+                float disp = mLowDisplayBrightnessThresholds[i];
+                float ambi = mLowAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
                     if (brightness <= disp && lux <= ambi) {
@@ -2045,10 +2069,18 @@
             return false;
         }
 
-        private boolean isInsideHighZone(int brightness, float lux) {
+        /**
+         * Check if we're in the high zone where higher refresh rates aren't allowed to prevent
+         * flickering.
+         * @param brightness The brightness value or a negative value meaning that only the lux
+         *                   threshold should be applied
+         * @param lux The lux value. If negative, only the brightness threshold is applied
+         * @return True if we're in the high zone
+         */
+        private boolean isInsideHighZone(float brightness, float lux) {
             for (int i = 0; i < mHighDisplayBrightnessThresholds.length; i++) {
-                int disp = mHighDisplayBrightnessThresholds[i];
-                int ambi = mHighAmbientBrightnessThresholds[i];
+                float disp = mHighDisplayBrightnessThresholds[i];
+                float ambi = mHighAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
                     if (brightness >= disp && lux >= ambi) {
@@ -2075,7 +2107,7 @@
             Vote refreshRateVote = null;
             Vote refreshRateSwitchingVote = null;
 
-            if (mBrightness < 0) {
+            if (Float.isNaN(mBrightness)) {
                 // Either the setting isn't available or we shouldn't be observing yet anyways.
                 // Either way, just bail out since there's nothing we can do here.
                 return;
@@ -2191,13 +2223,18 @@
             return mDefaultDisplayState == Display.STATE_ON;
         }
 
-        private int getBrightness(int displayId) {
+        /**
+         * Get the brightness value for a display
+         * @param displayId The ID of the display
+         * @return The brightness value
+         */
+        private float getBrightness(int displayId) {
             final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
             if (info != null) {
-                return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+                return info.adjustedBrightness;
             }
 
-            return BRIGHTNESS_INVALID;
+            return BRIGHTNESS_INVALID_FLOAT;
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
@@ -2285,7 +2322,7 @@
                 }
             }
 
-            private boolean isDifferentZone(float lux1, float lux2, int[] luxThresholds) {
+            private boolean isDifferentZone(float lux1, float lux2, float[] luxThresholds) {
                 for (final float boundary : luxThresholds) {
                     // Test each boundary. See if the current value and the new value are at
                     // different sides.
@@ -2719,9 +2756,9 @@
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
                     defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget();
 
-            int[] lowDisplayBrightnessThresholds =
+            float[] lowDisplayBrightnessThresholds =
                     mConfigParameterProvider.getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds =
+            float[] lowAmbientBrightnessThresholds =
                     mConfigParameterProvider.getLowAmbientBrightnessThresholds();
             final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
 
@@ -2732,9 +2769,9 @@
             mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
                     0).sendToTarget();
 
-            int[] highDisplayBrightnessThresholds =
+            float[] highDisplayBrightnessThresholds =
                     mConfigParameterProvider.getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds =
+            float[] highAmbientBrightnessThresholds =
                     mConfigParameterProvider.getHighAmbientBrightnessThresholds();
             final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
 
diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
index a8034c5..a7aee31 100644
--- a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
+++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
@@ -21,6 +21,7 @@
 import android.os.PowerManager;
 import android.util.Slog;
 
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.DisplayDeviceConfig;
 
 import java.util.ArrayList;
@@ -149,4 +150,43 @@
         }
         return value;
     }
+
+    /**
+     * Convert display brightness thresholds to a float array.
+     * @param thresholdsInt The int array of the thresholds in the range [0, 255]
+     * @return The float array of the thresholds
+     */
+    public static float[] displayBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
+        if (thresholdsInt == null) {
+            return null;
+        }
+
+        float[] thresholds = new float[thresholdsInt.length];
+        for (int i = 0; i < thresholds.length; i++) {
+            if (thresholdsInt[i] < 0) {
+                // A negative value means that there's no threshold
+                thresholds[i] = thresholdsInt[i];
+            } else {
+                thresholds[i] = BrightnessSynchronizer.brightnessIntToFloat(thresholdsInt[i]);
+            }
+        }
+        return thresholds;
+    }
+
+    /**
+     * Convert ambient brightness thresholds to a float array.
+     * @param thresholdsInt The int array of the thresholds
+     * @return The float array of the thresholds
+     */
+    public static float[] ambientBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
+        if (thresholdsInt == null) {
+            return null;
+        }
+
+        float[] thresholds = new float[thresholdsInt.length];
+        for (int i = 0; i < thresholds.length; i++) {
+            thresholds[i] = thresholdsInt[i];
+        }
+        return thresholds;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f8bd328..cab90d2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2650,15 +2650,15 @@
     @Override
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         checkManageUsersPermission("setDefaultGuestRestrictions");
+        final List<UserInfo> guests = getGuestUsers();
+        synchronized (mRestrictionsLock) {
+            for (int i = 0; i < guests.size(); i++) {
+                updateUserRestrictionsInternalLR(restrictions, guests.get(i).id);
+            }
+        }
         synchronized (mGuestRestrictions) {
             mGuestRestrictions.clear();
             mGuestRestrictions.putAll(restrictions);
-            final List<UserInfo> guests = getGuestUsers();
-            for (int i = 0; i < guests.size(); i++) {
-                synchronized (mRestrictionsLock) {
-                    updateUserRestrictionsInternalLR(mGuestRestrictions, guests.get(i).id);
-                }
-            }
         }
         synchronized (mPackagesLock) {
             writeUserListLP();
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 394105a..7d3c87a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -37,6 +37,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.screenOrientationToString;
@@ -44,6 +45,7 @@
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -642,6 +644,10 @@
 
     @ScreenOrientation
     int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+        if (shouldApplyUserFullscreenOverride()) {
+            return SCREEN_ORIENTATION_USER;
+        }
+
         // In some cases (e.g. Kids app) we need to map the candidate orientation to some other
         // orientation.
         candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
@@ -1103,20 +1109,28 @@
     }
 
     boolean shouldApplyUserMinAspectRatioOverride() {
-        if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) {
+        if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                || mActivityRecord.mDisplayContent == null
+                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
         }
 
-        try {
-            final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager()
-                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
-            mUserAspectRatio = userAspectRatio;
-            return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET;
-        } catch (RemoteException e) {
-            // Don't apply user aspect ratio override
-            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+        return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
+    boolean shouldApplyUserFullscreenOverride() {
+        if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+                || mActivityRecord.mDisplayContent == null
+                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
         }
+
+        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+        return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
     float getUserMinAspectRatio() {
@@ -1137,6 +1151,16 @@
         }
     }
 
+    private int getUserMinAspectRatioOverrideCode() {
+        try {
+            return mActivityRecord.mAtmService.getPackageManager()
+                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+        }
+        return mUserAspectRatio;
+    }
+
     private float getDisplaySizeMinAspectRatio() {
         final DisplayArea displayArea = mActivityRecord.getDisplayArea();
         if (displayArea == null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 8b04eca..da7a6a1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,6 +17,9 @@
 package com.android.server.display;
 
 
+import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
+import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -31,6 +34,7 @@
 import android.content.res.TypedArray;
 import android.os.Temperature;
 import android.util.SparseArray;
+import android.util.Spline;
 import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -63,10 +67,15 @@
     private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
     private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
     private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
-    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
-    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
-    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
-    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000};
+    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE =
+            new int[]{10, 30, -1};
+    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 1, 21};
+    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160, -1};
+    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 30000};
+    private static final float[] NITS = {2, 500, 800};
+    private static final float[] BRIGHTNESS = {0, 0.62f, 1};
+    private static final Spline NITS_TO_BRIGHTNESS_SPLINE =
+            Spline.createSpline(NITS, BRIGHTNESS);
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
     private static final float ZERO_DELTA = 0.0f;
@@ -98,12 +107,9 @@
         assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
@@ -171,14 +177,6 @@
         assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
         assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
         assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
-        assertArrayEquals(new int[]{45, 55},
-                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{50, 60},
-                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
-        assertArrayEquals(new int[]{65, 75},
-                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{70, 80},
-                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
 
         assertEquals("sensor_12345",
                 mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
@@ -343,14 +341,6 @@
                 DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
         assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
                 DEFAULT_REFRESH_RATE_IN_HBM_HDR);
-        assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
-                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
-                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(),
-                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
-                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
 
         // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
@@ -412,6 +402,42 @@
         assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
     }
 
+    @Test
+    public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1},
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{50, 60, -1, 60},
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1},
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{70, 80, -1, 80},
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
+    @Test
+    public void testBlockingZoneThresholdsFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
     private String getValidLuxThrottling() {
         return "<luxThrottling>\n"
                 + "    <brightnessLimitMap>\n"
@@ -525,16 +551,16 @@
                 +   "<name>Example Display</name>"
                 +   "<screenBrightnessMap>\n"
                 +       "<point>\n"
-                +           "<value>0.0</value>\n"
-                +           "<nits>2.0</nits>\n"
+                +           "<value>" + BRIGHTNESS[0] + "</value>\n"
+                +           "<nits>" + NITS[0] + "</nits>\n"
                 +       "</point>\n"
                 +       "<point>\n"
-                +           "<value>0.62</value>\n"
-                +           "<nits>500.0</nits>\n"
+                +           "<value>" + BRIGHTNESS[1] + "</value>\n"
+                +           "<nits>" + NITS[1] + "</nits>\n"
                 +       "</point>\n"
                 +       "<point>\n"
-                +           "<value>1.0</value>\n"
-                +           "<nits>800.0</nits>\n"
+                +           "<value>" + BRIGHTNESS[2] + "</value>\n"
+                +           "<nits>" + NITS[2] + "</nits>\n"
                 +       "</point>\n"
                 +   "</screenBrightnessMap>\n"
                 +   "<autoBrightness>\n"
@@ -799,13 +825,19 @@
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>50</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>45.3</nits>\n"
+                +                   "<nits>50</nits>\n"
                 +               "</displayBrightnessPoint>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>60</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>55.2</nits>\n"
+                +                   "<nits>300</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>-1</lux>\n"
+                +                   "<nits>300</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>60</lux>\n"
+                +                   "<nits>-1</nits>\n"
                 +               "</displayBrightnessPoint>\n"
                 +           "</blockingZoneThreshold>\n"
                 +       "</lowerBlockingZoneConfigs>\n"
@@ -814,13 +846,19 @@
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>70</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>65.6</nits>\n"
+                +                   "<nits>80</nits>\n"
                 +               "</displayBrightnessPoint>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>80</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>75</nits>\n"
+                +                   "<nits>100</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>-1</lux>\n"
+                +                   "<nits>100</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>80</lux>\n"
+                +                   "<nits>-1</nits>\n"
                 +               "</displayBrightnessPoint>\n"
                 +           "</blockingZoneThreshold>\n"
                 +       "</higherBlockingZoneConfigs>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 04273d6..15f13cd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -897,10 +897,12 @@
         config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
         director.start(sensorManager);
 
+        float[] expectedDisplayThresholds = { BrightnessSynchronizer.brightnessIntToFloat(10) };
+        float[] expectedAmbientThresholds = { 20 };
         assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
-                .isEqualTo(initialDisplayThresholds);
+                .isEqualTo(expectedDisplayThresholds);
         assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
-                .isEqualTo(initialAmbientThresholds);
+                .isEqualTo(expectedAmbientThresholds);
 
         final int[] updatedDisplayThresholds = { 9, 14 };
         final int[] updatedAmbientThresholds = { -1, 19 };
@@ -908,10 +910,14 @@
         config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
         // Need to wait for the property change to propagate to the main thread.
         waitForIdleSync();
+
+        expectedDisplayThresholds = new float[]{ BrightnessSynchronizer.brightnessIntToFloat(9),
+                BrightnessSynchronizer.brightnessIntToFloat(14) };
+        expectedAmbientThresholds = new float[]{ -1, 19 };
         assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
-                .isEqualTo(updatedDisplayThresholds);
+                .isEqualTo(expectedDisplayThresholds);
         assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
-                .isEqualTo(updatedAmbientThresholds);
+                .isEqualTo(expectedAmbientThresholds);
     }
 
     @Test
@@ -2370,14 +2376,14 @@
         assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 95);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 100);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{250});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{7000});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{5});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{10});
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(250) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{7000}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(5) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{10}, /* delta= */ 0);
 
 
         // Notify that the default display is updated, such that DisplayDeviceConfig has new values
@@ -2386,10 +2392,14 @@
         when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
         when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
         when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
-        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
-        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
-        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
-        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+        when(displayDeviceConfig.getLowDisplayBrightnessThresholds())
+                .thenReturn(new float[]{0.025f});
+        when(displayDeviceConfig.getLowAmbientBrightnessThresholds())
+                .thenReturn(new float[]{30});
+        when(displayDeviceConfig.getHighDisplayBrightnessThresholds())
+                .thenReturn(new float[]{0.21f});
+        when(displayDeviceConfig.getHighAmbientBrightnessThresholds())
+                .thenReturn(new float[]{2100});
         when(displayDeviceConfig.getDefaultRefreshRateInHbmHdr()).thenReturn(65);
         when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
         director.defaultDisplayDeviceUpdated(displayDeviceConfig);
@@ -2400,14 +2410,14 @@
                 0.0);
         assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
         assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{210});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{2100});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{25});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{30});
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{0.21f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{2100}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{0.025f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{30}, /* delta= */ 0);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
 
@@ -2431,14 +2441,14 @@
                 0.0);
         assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
         assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{255});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{8000});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{10});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{20});
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(255) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{8000}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(10) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{20}, /* delta= */ 0);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
 
@@ -2460,14 +2470,14 @@
                 0.0);
         assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
         assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{210});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{2100});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{25});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{30});
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{0.21f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{2100}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{0.025f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{30}, /* delta= */ 0);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
         assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
     }
@@ -2503,10 +2513,10 @@
         DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
         when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
         when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
-        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
-        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
-        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
-        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[]{0.025f});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[]{30});
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[]{0.21f});
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[]{2100});
 
         Resources resMock = mock(Resources.class);
         when(resMock.getInteger(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
index 5e28e63..83858cf 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
@@ -16,7 +16,9 @@
 
 package com.android.server.display.utils;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 
 import android.os.PowerManager;
@@ -25,6 +27,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.annotations.Keep;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.DisplayDeviceConfig;
 
 import org.junit.Test;
@@ -158,4 +161,29 @@
 
         assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
     }
+
+    @Test
+    public void testDisplayBrightnessThresholdsIntToFloat_Null() {
+        assertNull(DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(null));
+    }
+
+    @Test
+    public void testDisplayBrightnessThresholdsIntToFloat() {
+        assertArrayEquals(new float[]{ BrightnessSynchronizer.brightnessIntToFloat(155), -1,
+                        BrightnessSynchronizer.brightnessIntToFloat(170) },
+                DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                        new int[]{ 155, -1, 170 }), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testAmbientBrightnessThresholdsIntToFloat_Null() {
+        assertNull(DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(null));
+    }
+
+    @Test
+    public void testAmbientBrightnessThresholdsIntToFloat() {
+        assertArrayEquals(new float[]{ 1700, 20000, -1 },
+                DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                        new int[]{ 1700, 20000, -1 }), FLOAT_TOLERANCE);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 81a3794..72ab18d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -36,6 +36,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -778,6 +779,34 @@
                 /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
     }
 
+    @Test
+    public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
+    public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
+    public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() {
+        spyOn(mController);
+        doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
     // shouldUseDisplayLandscapeNaturalOrientation
 
     @Test