Merge "swipe-dismiss: isolate animation definition" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f40cbb6..7d324f8ca 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14351,11 +14351,13 @@
     method public void execSQL(String, Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+    method public long getLastChangedRowCount();
     method public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
+    method public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(String, String, android.content.ContentValues);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index cda9ae3..b1feb41 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -15,6 +15,7 @@
 
   @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
+    method public final boolean isResumed();
   }
 
   public class ActivityManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 76d4386..3429c7c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3285,6 +3285,9 @@
     field @Deprecated protected int mCapabilities;
   }
 
+  public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+  }
+
 }
 
 package android.text {
@@ -3876,6 +3879,9 @@
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
 
+  public final class InsertModeGesture extends android.view.inputmethod.CancellableHandwritingGesture implements android.os.Parcelable {
+  }
+
 }
 
 package android.view.inspector {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5acf03..a366464 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2049,7 +2049,7 @@
      * indicator that the activity became active and ready to receive input. This sometimes could
      * also be a transit state toward another resting state. For instance, an activity may be
      * relaunched to {@link #onPause} due to configuration changes and the activity was visible,
-     * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be
+     * but wasn't the top-most activity of an activity task. {@link #onResume} is guaranteed to be
      * called before {@link #onPause} in this case which honors the activity lifecycle policy and
      * the activity eventually rests in {@link #onPause}.
      *
@@ -9063,6 +9063,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public final boolean isResumed() {
         return mResumed;
     }
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..729e555 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -195,8 +195,7 @@
         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
         int outerDepth = parser.getDepth();
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        // LinkedHashSet to preserve insertion order
-        Set<String> localeNames = new LinkedHashSet<>();
+        Set<String> localeNames = new HashSet<String>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (TAG_LOCALE.equals(parser.getName())) {
                 final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
  * A representation of settings that apply to a collection of similarly themed notifications.
  */
 public final class NotificationChannel implements Parcelable {
+    private static final String TAG = "NotificationChannel";
 
     /**
      * The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
 
         Uri sound = safeUri(parser, ATT_SOUND);
-        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
-                safeAudioAttributes(parser));
+
+        final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+        final int usage = audioAttributes.getUsage();
+        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+                audioAttributes);
 
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-
         return contentResolver.canonicalize(uri);
     }
 
     @Nullable
-    private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+    private Uri getUncanonicalizedSoundUri(
+            ContentResolver contentResolver, @NonNull Uri uri, int usage) {
         if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
                 || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-        return contentResolver.uncanonicalize(uri);
+        int ringtoneType = 0;
+
+        // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+        if (AudioAttributes.USAGE_ALARM == usage) {
+            ringtoneType = RingtoneManager.TYPE_ALARM;
+        } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+            ringtoneType = RingtoneManager.TYPE_RINGTONE;
+        } else {
+            ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+        }
+        try {
+            return RingtoneManager.getRingtoneUriForRestore(
+                    contentResolver, uri.toString(), ringtoneType);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
     }
 
     /**
@@ -1033,7 +1055,8 @@
      * @hide
      */
     @Nullable
-    public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+    public Uri restoreSoundUri(
+            Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
         if (uri == null || Uri.EMPTY.equals(uri)) {
             return null;
         }
@@ -1060,7 +1083,7 @@
             }
         }
         mSoundRestored = true;
-        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
     }
 
     /**
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
index 270cb18..62ad8c0 100644
--- a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -1,4 +1,3 @@
+file:EnterprisePlatform_OWNERS
 rubinxu@google.com
 pgrafov@google.com
-ayushsha@google.com
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatformTest_OWNERS b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
new file mode 100644
index 0000000..eb23a03
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
@@ -0,0 +1,5 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1337891&template=1814288
+# Assign bugs to aep-automated-tests@google.com
+
+file:EnterprisePlatform_OWNERS
+scottjonathan@google.com
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
index 6ce25cc..4d1ed590 100644
--- a/core/java/android/app/admin/EnterprisePlatform_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -1,5 +1,2 @@
-# Assign bugs to android-enterprise-triage@google.com
-file:WorkDeviceExperience_OWNERS
-file:Provisioning_OWNERS
-file:WorkProfile_OWNERS
-file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
+sandness@google.com #{LAST_RESORT_SUGGESTION}
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 10a5f14..308f1d6 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,5 +1,7 @@
 # Bug component: 142675
+# Assign bugs to device-policy-manager-triage@google.com
 
-file:EnterprisePlatform_OWNERS
+file:WorkDeviceExperience_OWNERS
+file:EnterprisePlatformSecurity_OWNERS
 
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index 2e5c2df..fa0a1f0 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,5 +1,4 @@
 # Assign bugs to android-enterprise-triage@google.com
 mdb.ae-provisioning-reviews@google.com
-petuska@google.com #{LAST_RESORT_SUGGESTION}
-nupursn@google.com #{LAST_RESORT_SUGGESTION}
-shreyacsingh@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
+petuska@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
index 2033343..10ac59a 100644
--- a/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -1,7 +1,5 @@
 # Assign bugs to android-enterprise-triage@google.com
 work-device-experience+reviews@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-eliselliott@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
 kholoudm@google.com #{LAST_RESORT_SUGGESTION}
 acjohnston@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/admin/WorkProfile_OWNERS b/core/java/android/app/admin/WorkProfile_OWNERS
index 260b672..e9bcce2 100644
--- a/core/java/android/app/admin/WorkProfile_OWNERS
+++ b/core/java/android/app/admin/WorkProfile_OWNERS
@@ -1,5 +1,3 @@
 # Assign bugs to android-enterprise-triage@google.com
-liahav@google.com
-olit@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
+file:EnterprisePlatform_OWNERS
+liahav@google.com
\ No newline at end of file
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index e01543d..a3b202a 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -119,4 +119,8 @@
     void setAssociationTag(int associationId, String tag);
 
     void clearAssociationTag(int associationId);
+
+    byte[] getBackupPayload(int userId);
+
+    void applyRestoredPayload(in byte[] payload, int userId);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 508eeed..048289f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
 
     private static AssetManager newConfiguredAssetManager() {
         AssetManager assetManager = new AssetManager();
-        assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         return assetManager;
     }
 
@@ -9011,8 +9011,8 @@
             }
 
             AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
             mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
 
         private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
             final AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
             return assets;
         }
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 23b9d0b..b225de4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,13 +1480,9 @@
             int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
             int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
             int majorVersion) {
-        if (locale != null) {
-            setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
-                    density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
-                    smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
-                    colorMode, grammaticalGender, majorVersion);
-        } else {
-            setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
+        synchronized (this) {
+            ensureValidLocked();
+            nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
                     keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
                     smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
                     colorMode, grammaticalGender, majorVersion);
@@ -1494,25 +1490,6 @@
     }
 
     /**
-     * Change the configuration used when retrieving resources.  Not for use by
-     * applications.
-     * @hide
-     */
-    public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
-            int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
-            int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
-            int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
-            int grammaticalGender, int majorVersion) {
-        synchronized (this) {
-            ensureValidLocked();
-            nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
-                    touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
-                    screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
-                    screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
-        }
-    }
-
-    /**
      * @hide
      */
     @UnsupportedAppUsage
@@ -1595,11 +1572,10 @@
     private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
             boolean invalidateCaches);
     private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
-            @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
-            int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
-            int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
-            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
-            int majorVersion);
+            @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+            int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+            int uiMode, int colorMode, int grammaticalGender, int majorVersion);
     private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
             long ptr, boolean includeOverlays, boolean includeLoaders);
 
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index a937832..395fef2 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -425,12 +425,14 @@
                     mConfiguration.setLocales(locales);
                 }
 
-                String[] selectedLocales = null;
-                String defaultLocale = null;
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
                         String[] availableLocales;
-                        if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
+
+                        LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
+                        if (!localeList.isEmpty()) {
+                            availableLocales = localeList.toLanguageTags().split(",");
+                        } else {
                             // The LocaleList has changed. We must query the AssetManager's
                             // available Locales and figure out the best matching Locale in the new
                             // LocaleList.
@@ -442,30 +444,14 @@
                                     availableLocales = null;
                                 }
                             }
-                            if (availableLocales != null) {
-                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                        availableLocales);
-                                if (bestLocale != null) {
-                                    selectedLocales = new String[]{
-                                            adjustLanguageTag(bestLocale.toLanguageTag())};
-                                    if (!bestLocale.equals(locales.get(0))) {
-                                        mConfiguration.setLocales(
-                                                new LocaleList(bestLocale, locales));
-                                    }
-                                }
-                            }
-                        } else {
-                            selectedLocales = locales.getIntersection(
-                                    ResourcesManager.getInstance().getLocaleList());
-                            defaultLocale = ResourcesManager.getInstance()
-                                    .getLocaleList().get(0).toLanguageTag();
                         }
-                    }
-                }
-                if (selectedLocales == null) {
-                    selectedLocales = new String[locales.size()];
-                    for (int i = 0; i < locales.size(); i++) {
-                        selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                        if (availableLocales != null) {
+                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                    availableLocales);
+                            if (bestLocale != null && bestLocale != locales.get(0)) {
+                                mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+                            }
+                        }
                     }
                 }
 
@@ -502,8 +488,7 @@
                 }
 
                 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
-                        defaultLocale,
-                        selectedLocales,
+                        adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
                         mConfiguration.orientation,
                         mConfiguration.touchscreen,
                         mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 706e75e..f2980f4 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1875,7 +1875,7 @@
      * statement
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         try {
             return nativeChanges(mConnectionPtr);
         } finally {
@@ -1887,7 +1887,7 @@
      * Return the total number of database changes made on the current connection.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         try {
             return nativeTotalChanges(mConnectionPtr);
         } finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index a3f8383..aec01f8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2208,10 +2208,9 @@
      *
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getLastChangedRowsCount() {
-        return getThreadSession().getLastChangedRowsCount();
+    public long getLastChangedRowCount() {
+        return getThreadSession().getLastChangedRowCount();
     }
 
     /**
@@ -2223,9 +2222,9 @@
      * <code><pre>
      *    database.beginTransaction();
      *    try {
-     *        long initialValue = database.getTotalChangedRowsCount();
+     *        long initialValue = database.getTotalChangedRowCount();
      *        // Execute SQL statements
-     *        long changedRows = database.getTotalChangedRowsCount() - initialValue;
+     *        long changedRows = database.getTotalChangedRowCount() - initialValue;
      *        // changedRows counts the total number of rows updated in the transaction.
      *    } finally {
      *        database.endTransaction();
@@ -2236,10 +2235,9 @@
      *
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getTotalChangedRowsCount() {
-        return getThreadSession().getTotalChangedRowsCount();
+    public long getTotalChangedRowCount() {
+        return getThreadSession().getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index ef1a9cb..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -998,9 +998,9 @@
      * this connection.
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getLastChangedRowsCount();
+        return mConnection.getLastChangedRowCount();
     }
 
     /**
@@ -1008,9 +1008,9 @@
      * it was created.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getTotalChangedRowsCount();
+        return mConnection.getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 889a43c..5ff0e7a 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.GraphicBuffer;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -399,11 +400,14 @@
     public static final @android.annotation.NonNull Parcelable.Creator<HardwareBuffer> CREATOR =
             new Parcelable.Creator<HardwareBuffer>() {
         public HardwareBuffer createFromParcel(Parcel in) {
+            if (in == null) {
+                throw new NullPointerException("null passed to createFromParcel");
+            }
             long nativeObject = nReadHardwareBufferFromParcel(in);
             if (nativeObject != 0) {
                 return new HardwareBuffer(nativeObject);
             }
-            return null;
+            throw new BadParcelableException("Failed to read hardware buffer");
         }
 
         public HardwareBuffer[] newArray(int size) {
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index a62d74e..22e3938 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -39,7 +39,8 @@
  * Holds configuration used to create {@link VirtualDisplay} instances.
  *
  * @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
- * @see MediaProjection#createVirtualDisplay
+ * @see MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)
  */
 public final class VirtualDisplayConfig implements Parcelable {
 
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index 66269cb..b25f47b 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -172,7 +172,8 @@
             String name = in.readString();
             int attributes = in.readInt();
             int maxPower = in.readInt();
-            Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
+            Parcelable[] interfaces = in.readParcelableArray(
+                    UsbInterface.class.getClassLoader(), UsbInterface.class);
             UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower);
             configuration.setInterfaces(interfaces);
             return configuration;
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..b74bb33 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,7 +30,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Locale;
 
 /**
@@ -152,25 +151,6 @@
     }
 
     /**
-     * Find the intersection between this LocaleList and another
-     * @return a String array of the Locales in both LocaleLists
-     * {@hide}
-     */
-    @NonNull
-    public String[] getIntersection(@NonNull LocaleList other) {
-        List<String> intersection = new ArrayList<>();
-        for (Locale l1 : mList) {
-            for (Locale l2 : other.mList) {
-                if (matchesLanguageAndScript(l2, l1)) {
-                    intersection.add(l1.toLanguageTag());
-                    break;
-                }
-            }
-        }
-        return intersection.toArray(new String[0]);
-    }
-
-    /**
      * Creates a new {@link LocaleList}.
      *
      * If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bf58eaa..a565b3b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11836,6 +11836,13 @@
                 "accessibility_display_magnification_edge_haptic_enabled";
 
         /**
+         * If 1, DND default allowed packages have been updated
+         *
+         *  @hide
+         */
+        public static final String DND_CONFIGS_MIGRATED = "dnd_settings_migrated";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 2db2132..ad46f2b 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -815,7 +815,7 @@
 
         int syncResult = syncAndDrawFrame(frameInfo);
         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
-            Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
+            Log.w("HWUI", "Surface lost, forcing relayout");
             // We lost our surface. For a relayout next frame which should give us a new
             // surface from WindowManager, which hopefully will work.
             attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9687a53..c877873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2116,7 +2116,7 @@
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
-            return fallbackImm.showSoftInput(view, flags, resultReceiver);
+            return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
         }
 
         checkFocus();
diff --git a/core/java/com/android/internal/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
index d3cc0e7..10856b3 100644
--- a/core/java/com/android/internal/util/NotificationMessagingUtil.java
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -39,10 +40,12 @@
 
     private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
     private final Context mContext;
-    private SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final Object mStateLock;
 
-    public NotificationMessagingUtil(Context context) {
+    public NotificationMessagingUtil(Context context, @Nullable Object stateLock) {
         mContext = context;
+        mStateLock = stateLock != null ? stateLock : new Object();
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
     }
@@ -63,16 +66,20 @@
     private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
         final int userId = sbn.getUserId();
         if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
-        if (mDefaultSmsApp.get(userId) == null) {
-            cacheDefaultSmsApp(userId);
+        synchronized (mStateLock) {
+            if (mDefaultSmsApp.get(userId) == null) {
+                cacheDefaultSmsApp(userId);
+            }
+            return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
         }
-        return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
     }
 
     private void cacheDefaultSmsApp(int userId) {
-        mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+        String smsApp = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+        synchronized (mStateLock) {
+            mDefaultSmsApp.put(userId, smsApp);
+        }
     }
 
     private final ContentObserver mSmsContentObserver = new ContentObserver(
diff --git a/core/java/com/android/server/backup/CompanionBackupHelper.java b/core/java/com/android/server/backup/CompanionBackupHelper.java
new file mode 100644
index 0000000..ef247c2
--- /dev/null
+++ b/core/java/com/android/server/backup/CompanionBackupHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.server.backup;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BlobBackupHelper;
+import android.companion.ICompanionDeviceManager;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * CDM backup and restore helper.
+ */
+public class CompanionBackupHelper extends BlobBackupHelper {
+
+    private static final String TAG = "CompanionBackupHelper";
+
+    // current schema of the backup state blob
+    private static final int BLOB_VERSION = 1;
+
+    // key under which the CDM data blob is committed to back up
+    private static final String KEY_COMPANION = "companion";
+
+    @UserIdInt
+    private final int mUserId;
+
+    public CompanionBackupHelper(int userId) {
+        super(BLOB_VERSION, KEY_COMPANION);
+
+        mUserId = userId;
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        byte[] payload = null;
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                payload = cdm.getBackupPayload(mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error getting backup from CompanionDeviceManager.", e);
+            }
+        }
+        return payload;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        Slog.i(TAG, "Got companion backup data.");
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                cdm.applyRestoredPayload(payload, mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error applying restored payload to CompanionDeviceManager.", e);
+            }
+        }
+    }
+}
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 5fcc46e..2ea2158d 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -203,10 +203,10 @@
     Parcel* parcel = parcelForJavaObject(env, in);
     if (parcel) {
         sp<GraphicBuffer> buffer = new GraphicBuffer();
-        parcel->read(*buffer);
-        return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        if (parcel->read(*buffer) == STATUS_OK) {
+            return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        }
     }
-
     return NULL;
 }
 
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 1afae29..979c9e3 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,23 +347,14 @@
 }
 
 static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
-                                   jstring default_locale, jobjectArray locales, jint orientation,
-                                   jint touchscreen, jint density, jint keyboard,
-                                   jint keyboard_hidden, jint navigation, jint screen_width,
-                                   jint screen_height, jint smallest_screen_width_dp,
-                                   jint screen_width_dp, jint screen_height_dp, jint screen_layout,
-                                   jint ui_mode, jint color_mode, jint grammatical_gender,
-                                   jint major_version) {
+                                   jstring locale, jint orientation, jint touchscreen, jint density,
+                                   jint keyboard, jint keyboard_hidden, jint navigation,
+                                   jint screen_width, jint screen_height,
+                                   jint smallest_screen_width_dp, jint screen_width_dp,
+                                   jint screen_height_dp, jint screen_layout, jint ui_mode,
+                                   jint color_mode, jint grammatical_gender, jint major_version) {
   ATRACE_NAME("AssetManager::SetConfiguration");
 
-  const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
-
-  // Constants duplicated from Java class android.content.res.Configuration.
-  static const jint kScreenLayoutRoundMask = 0x300;
-  static const jint kScreenLayoutRoundShift = 8;
-
-  std::vector<ResTable_config> configs;
-
   ResTable_config configuration;
   memset(&configuration, 0, sizeof(configuration));
   configuration.mcc = static_cast<uint16_t>(mcc);
@@ -384,37 +375,25 @@
   configuration.colorMode = static_cast<uint8_t>(color_mode);
   configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
   configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+  if (locale != nullptr) {
+    ScopedUtfChars locale_utf8(env, locale);
+    CHECK(locale_utf8.c_str() != nullptr);
+    configuration.setBcp47Locale(locale_utf8.c_str());
+  }
+
+  // Constants duplicated from Java class android.content.res.Configuration.
+  static const jint kScreenLayoutRoundMask = 0x300;
+  static const jint kScreenLayoutRoundShift = 8;
+
   // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
   // in C++. We must extract the round qualifier out of the Java screenLayout and put it
   // into screenLayout2.
   configuration.screenLayout2 =
-          static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
-  if (locale_count > 0) {
-    configs.resize(locale_count, configuration);
-    for (int i = 0; i < locale_count; i++) {
-      jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
-      ScopedUtfChars locale_utf8(env, locale);
-      CHECK(locale_utf8.c_str() != nullptr);
-      configs[i].setBcp47Locale(locale_utf8.c_str());
-    }
-  } else {
-    configs.push_back(configuration);
-  }
-
-  uint32_t default_locale_int = 0;
-  if (default_locale != nullptr) {
-    ResTable_config config;
-    static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
-    ScopedUtfChars locale_utf8(env, default_locale);
-    CHECK(locale_utf8.c_str() != nullptr);
-    config.setBcp47Locale(locale_utf8.c_str());
-    default_locale_int = config.locale;
-  }
+      static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
 
   auto assetmanager = LockAndStartAssetManager(ptr);
-  assetmanager->SetConfigurations(configs);
-  assetmanager->SetDefaultLocale(default_locale_int);
+  assetmanager->SetConfiguration(configuration);
 }
 
 static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1519,97 +1498,94 @@
 
 // JNI registration.
 static const JNINativeMethod gAssetManagerMethods[] = {
-        // AssetManager setup methods.
-        {"nativeCreate", "()J", (void*)NativeCreate},
-        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-        {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
-        {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
-         (void*)NativeSetConfiguration},
-        {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
-         (void*)NativeGetAssignedPackageIdentifiers},
+    // AssetManager setup methods.
+    {"nativeCreate", "()J", (void*)NativeCreate},
+    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+    {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
+     (void*)NativeSetConfiguration},
+    {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+     (void*)NativeGetAssignedPackageIdentifiers},
 
-        // AssetManager file methods.
-        {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
-        {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
-        {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
-        {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-         (void*)NativeOpenAssetFd},
-        {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
-        {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-         (void*)NativeOpenNonAssetFd},
-        {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
-        {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+    // AssetManager file methods.
+    {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+    {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+    {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+    {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenAssetFd},
+    {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+    {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenNonAssetFd},
+    {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+    {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
 
-        // AssetManager resource methods.
-        {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
-         (void*)NativeGetResourceValue},
-        {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
-         (void*)NativeGetResourceBagValue},
-        {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
-        {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
-         (void*)NativeGetResourceStringArray},
-        {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
-        {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
-        {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
-        {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
-        {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
+    // AssetManager resource methods.
+    {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+    {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+     (void*)NativeGetResourceBagValue},
+    {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+    {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+     (void*)NativeGetResourceStringArray},
+    {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+    {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+    {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+    {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+    {"nativeGetParentThemeIdentifier", "(JI)I",
+     (void*)NativeGetParentThemeIdentifier},
 
-        // AssetManager resource name/ID methods.
-        {"nativeGetResourceIdentifier",
-         "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-         (void*)NativeGetResourceIdentifier},
-        {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
-        {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
-         (void*)NativeGetResourcePackageName},
-        {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
-        {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
-        {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
-         (void*)NativeSetResourceResolutionLoggingEnabled},
-        {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
-         (void*)NativeGetLastResourceResolution},
-        {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
-        {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
-         (void*)NativeGetSizeConfigurations},
-        {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
-         (void*)NativeGetSizeAndUiModeConfigurations},
+    // AssetManager resource name/ID methods.
+    {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+     (void*)NativeGetResourceIdentifier},
+    {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+    {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+    {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+    {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+    {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+     (void*) NativeSetResourceResolutionLoggingEnabled},
+    {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+     (void*) NativeGetLastResourceResolution},
+    {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+    {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+     (void*)NativeGetSizeConfigurations},
+    {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+     (void*)NativeGetSizeAndUiModeConfigurations},
 
-        // Style attribute related methods.
-        {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
-        {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
-        {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
-        {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+    // Style attribute related methods.
+    {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+    {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+    {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+    {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
 
-        // Theme related methods.
-        {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
-        {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
-        {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
-        {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+    // Theme related methods.
+    {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+    {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+    {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+    {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
 
-        {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
-        {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
-         (void*)NativeThemeGetAttributeValue},
-        {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
-        {"nativeThemeGetChangingConfigurations", "(J)I",
-         (void*)NativeThemeGetChangingConfigurations},
+    {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+    {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+     (void*)NativeThemeGetAttributeValue},
+    {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+    {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
 
-        // AssetInputStream methods.
-        {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
-        {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
-        {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
-        {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
-        {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
-        {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+    // AssetInputStream methods.
+    {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+    {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+    {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+    {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+    {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+    {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
 
-        // System/idmap related methods.
-        {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
-         (void*)NativeGetOverlayableMap},
-        {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
-         (void*)NativeGetOverlayablesToString},
+    // System/idmap related methods.
+    {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+     (void*)NativeGetOverlayableMap},
+    {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+     (void*)NativeGetOverlayablesToString},
 
-        // Global management/debug methods.
-        {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
-        {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
-        {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+    // Global management/debug methods.
+    {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+    {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+    {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
 };
 
 int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 71630cb..73a1abc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4288,6 +4288,9 @@
     <!-- Colon separated list of package names that should be granted DND access -->
     <string name="config_defaultDndAccessPackages" translatable="false">com.android.camera2</string>
 
+    <!-- Colon separated list of package names that should be removed from DND access packages -->
+    <string name="config_defaultDndDeniedPackages" translatable="false"></string>
+
     <!-- User restrictions set on the SYSTEM user when it is first created.
          Note: Also update appropriate overlay files. -->
     <string-array translatable="false" name="config_defaultFirstUserRestrictions">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8a395f8..b14bf5b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3381,6 +3381,8 @@
 
   <!-- Colon separated list of package names that should be granted DND access -->
   <java-symbol type="string" name="config_defaultDndAccessPackages" />
+  <!-- Colon separated list of package names that should be removed from DND access packages -->
+  <java-symbol type="string" name="config_defaultDndDeniedPackages" />
 
   <!-- For NetworkPolicyManagerService -->
   <java-symbol type="string" name="config_networkOverLimitComponent" />
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
 
 package android.app;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertEquals;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
 import com.google.common.base.Strings;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
 
 @RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
 public class NotificationChannelTest {
     private final String CLASS = "android.app.NotificationChannel";
 
+    Context mContext;
+    ContentProvider mContentProvider;
+    IContentProvider mIContentProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = mock(Context.class);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        MockContentResolver mContentResolver = new MockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        mContentProvider = mock(ContentProvider.class);
+        mIContentProvider = mock(IContentProvider.class);
+        when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.canonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .canonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.uncanonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .uncanonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        Uri uri = invocation.getArgument(0);
+                        RemoteCallback cb = invocation.getArgument(1);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putString(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.getType(uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .getTypeAsync(any(), any());
+
+        mContentResolver.addProvider("media", mContentProvider);
+    }
+
     @Test
     public void testLongStringFields() {
         NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
         assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
                 fromParcel.getSound().toString().length());
     }
+
+    @Test
+    public void testRestoreSoundUri_customLookup() throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        assertThat(
+                        channel.restoreSoundUri(
+                                mContext,
+                                uriToBeRestoredUncanonicalized,
+                                true,
+                                AudioAttributes.USAGE_NOTIFICATION))
+                .isEqualTo(uriAfterRestoredCanonicalized);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+    }
+
+    private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+            throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        AudioAttributes mAudioAttributes =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+                        .setUsage(usage)
+                        .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                        .build();
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+
+        // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        channel.writeXmlForBackup(serializer, mContext);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        byte[] byteArray = baos.toByteArray();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+        parser.nextTag();
+
+        NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        Bundle expectedBundle =
+                ContentResolver.createSqlQueryBundle(
+                        customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(
+                        any(),
+                        any(),
+                        any(),
+                        // any(),
+                        argThat(
+                                queryBundle -> {
+                                    return queryBundle != null
+                                            && expectedBundle
+                                                    .toString()
+                                                    .equals(queryBundle.toString());
+                                }),
+                        any()))
+                .thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        targetChannel.populateFromXmlForRestore(parser, true, mContext);
+        assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+    }
 }
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index fc72f61..4ee987b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -180,8 +180,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -205,8 +205,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -214,4 +214,21 @@
             mDatabase.endTransaction();
         }
     }
+
+    @Test
+    public void testAutomaticCountersOutsideTransactions() {
+        try {
+            mDatabase.getLastChangedRowCount();
+            fail("getLastChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+
+        try {
+            mDatabase.getTotalChangedRowCount();
+            fail("getTotalChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+    }
 }
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5ffec34..b1ef4e5 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,17 +91,12 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
-  configurations_.push_back(configuration);
-
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
+    : configuration_(configuration) {
   // Don't invalidate caches here as there's nothing cached yet.
   SetApkAssets(apk_assets, false);
 }
 
-AssetManager2::AssetManager2() {
-  configurations_.resize(1);
-}
-
 bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
   BuildDynamicRefTable(apk_assets);
   RebuildFilterList();
@@ -426,16 +421,9 @@
   return false;
 }
 
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
-  int diff = 0;
-  if (configurations_.size() != configurations.size()) {
-    diff = -1;
-  } else {
-    for (int i = 0; i < configurations_.size(); i++) {
-      diff |= configurations_[i].diff(configurations[i]);
-    }
-  }
-  configurations_ = std::move(configurations);
+void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
+  const int diff = configuration_.diff(configuration);
+  configuration_ = configuration;
 
   if (diff) {
     RebuildFilterList();
@@ -632,6 +620,16 @@
 
   auto op = StartOperation();
 
+  // Might use this if density_override != 0.
+  ResTable_config density_override_config;
+
+  // Select our configuration or generate a density override configuration.
+  const ResTable_config* desired_config = &configuration_;
+  if (density_override != 0 && density_override != configuration_.density) {
+    density_override_config = configuration_;
+    density_override_config.density = density_override;
+    desired_config = &density_override_config;
+  }
 
   // Retrieve the package group from the package id of the resource id.
   if (UNLIKELY(!is_valid_resid(resid))) {
@@ -650,160 +648,119 @@
   }
 
   const PackageGroup& package_group = package_groups_[package_idx];
-  std::optional<FindEntryResult> final_result;
-  bool final_has_locale = false;
-  bool final_overlaid = false;
-  for (auto & config : configurations_) {
-    // Might use this if density_override != 0.
-    ResTable_config density_override_config;
+  auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+                                  stop_at_first_match, ignore_configuration);
+  if (UNLIKELY(!result.has_value())) {
+    return base::unexpected(result.error());
+  }
 
-    // Select our configuration or generate a density override configuration.
-    const ResTable_config* desired_config = &config;
-    if (density_override != 0 && density_override != config.density) {
-      density_override_config = config;
-      density_override_config.density = density_override;
-      desired_config = &density_override_config;
+  bool overlaid = false;
+  if (!stop_at_first_match && !ignore_configuration) {
+    const auto& assets = GetApkAssets(result->cookie);
+    if (!assets) {
+      ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+      return base::unexpected(std::nullopt);
     }
-
-    auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
-                                    stop_at_first_match, ignore_configuration);
-    if (UNLIKELY(!result.has_value())) {
-      return base::unexpected(result.error());
-    }
-    bool overlaid = false;
-    if (!stop_at_first_match && !ignore_configuration) {
-      const auto& assets = GetApkAssets(result->cookie);
-      if (!assets) {
-        ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
-        return base::unexpected(std::nullopt);
-      }
-      if (!assets->IsLoader()) {
-        for (const auto& id_map : package_group.overlays_) {
-          auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
-          if (!overlay_entry) {
-            // No id map entry exists for this target resource.
-            continue;
-          }
-          if (overlay_entry.IsInlineValue()) {
-            // The target resource is overlaid by an inline value not represented by a resource.
-            ConfigDescription best_frro_config;
-            Res_value best_frro_value;
-            bool frro_found = false;
-            for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
-              if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
-                  && config.match(*desired_config)) {
-                frro_found = true;
-                best_frro_config = config;
-                best_frro_value = value;
-              }
+    if (!assets->IsLoader()) {
+      for (const auto& id_map : package_group.overlays_) {
+        auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+        if (!overlay_entry) {
+          // No id map entry exists for this target resource.
+          continue;
+        }
+        if (overlay_entry.IsInlineValue()) {
+          // The target resource is overlaid by an inline value not represented by a resource.
+          ConfigDescription best_frro_config;
+          Res_value best_frro_value;
+          bool frro_found = false;
+          for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+            if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+                && config.match(*desired_config)) {
+              frro_found = true;
+              best_frro_config = config;
+              best_frro_value = value;
             }
-            if (!frro_found) {
-              continue;
-            }
-            result->entry = best_frro_value;
-            result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-            result->cookie = id_map.cookie;
-
-            if (UNLIKELY(logging_enabled)) {
-              last_resolution_.steps.push_back(Resolution::Step{
-                  Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
-              if (auto path = assets->GetPath()) {
-                const std::string overlay_path = path->data();
-                if (IsFabricatedOverlay(overlay_path)) {
-                  // FRRO don't have package name so we use the creating package here.
-                  String8 frro_name = String8("FRRO");
-                  // Get the first part of it since the expected one should be like
-                  // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
-                  // under /data/resource-cache/.
-                  const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
-                  const size_t end = name.find('-');
-                  if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
-                    frro_name.append(base::StringPrintf(" created by %s",
-                                                        name.substr(0 /* pos */,
-                                                                    end).c_str()).c_str());
-                  }
-                  last_resolution_.best_package_name = frro_name;
-                } else {
-                  last_resolution_.best_package_name = result->package_name->c_str();
-                }
-              }
-              overlaid = true;
-            }
+          }
+          if (!frro_found) {
             continue;
           }
-
-          auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
-                                          false /* stop_at_first_match */,
-                                          false /* ignore_configuration */);
-          if (UNLIKELY(IsIOError(overlay_result))) {
-            return base::unexpected(overlay_result.error());
-          }
-          if (!overlay_result.has_value()) {
-            continue;
-          }
-
-          if (!overlay_result->config.isBetterThan(result->config, desired_config)
-              && overlay_result->config.compare(result->config) != 0) {
-            // The configuration of the entry for the overlay must be equal to or better than the
-            // target configuration to be chosen as the better value.
-            continue;
-          }
-
-          result->cookie = overlay_result->cookie;
-          result->entry = overlay_result->entry;
-          result->config = overlay_result->config;
+          result->entry = best_frro_value;
           result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+          result->cookie = id_map.cookie;
 
           if (UNLIKELY(logging_enabled)) {
             last_resolution_.steps.push_back(
-                Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
-                                 overlay_result->config.toString()});
-            last_resolution_.best_package_name =
-                overlay_result->package_name->c_str();
+                Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+            if (auto path = assets->GetPath()) {
+              const std::string overlay_path = path->data();
+              if (IsFabricatedOverlay(overlay_path)) {
+                // FRRO don't have package name so we use the creating package here.
+                String8 frro_name = String8("FRRO");
+                // Get the first part of it since the expected one should be like
+                // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+                // under /data/resource-cache/.
+                const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+                const size_t end = name.find('-');
+                if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+                  frro_name.append(base::StringPrintf(" created by %s",
+                                                      name.substr(0 /* pos */,
+                                                                  end).c_str()).c_str());
+                }
+                last_resolution_.best_package_name = frro_name;
+              } else {
+                last_resolution_.best_package_name = result->package_name->c_str();
+              }
+            }
             overlaid = true;
           }
+          continue;
+        }
+
+        auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+                                        false /* stop_at_first_match */,
+                                        false /* ignore_configuration */);
+        if (UNLIKELY(IsIOError(overlay_result))) {
+          return base::unexpected(overlay_result.error());
+        }
+        if (!overlay_result.has_value()) {
+          continue;
+        }
+
+        if (!overlay_result->config.isBetterThan(result->config, desired_config)
+            && overlay_result->config.compare(result->config) != 0) {
+          // The configuration of the entry for the overlay must be equal to or better than the target
+          // configuration to be chosen as the better value.
+          continue;
+        }
+
+        result->cookie = overlay_result->cookie;
+        result->entry = overlay_result->entry;
+        result->config = overlay_result->config;
+        result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+
+        if (UNLIKELY(logging_enabled)) {
+          last_resolution_.steps.push_back(
+              Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+                               overlay_result->config.toString()});
+          last_resolution_.best_package_name =
+              overlay_result->package_name->c_str();
+          overlaid = true;
         }
       }
     }
-
-    bool has_locale = false;
-    if (result->config.locale == 0) {
-      if (default_locale_ != 0) {
-        ResTable_config conf;
-        conf.locale = default_locale_;
-        // Since we know conf has a locale and only a locale, match will tell us if that locale
-        // matches
-        has_locale = conf.match(config);
-      }
-    } else {
-      has_locale = true;
-    }
-
-    // if we don't have a result yet
-    if (!final_result ||
-        // or this config is better before the locale than the existing result
-        result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
-        // or the existing config isn't better before locale and this one specifies a locale
-        // whereas the existing one doesn't
-        (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
-            && has_locale && !final_has_locale)) {
-      final_result = result.value();
-      final_overlaid = overlaid;
-      final_has_locale = has_locale;
-    }
   }
 
   if (UNLIKELY(logging_enabled)) {
-    last_resolution_.cookie = final_result->cookie;
-    last_resolution_.type_string_ref = final_result->type_string_ref;
-    last_resolution_.entry_string_ref = final_result->entry_string_ref;
-    last_resolution_.best_config_name = final_result->config.toString();
-    if (!final_overlaid) {
-      last_resolution_.best_package_name = final_result->package_name->c_str();
+    last_resolution_.cookie = result->cookie;
+    last_resolution_.type_string_ref = result->type_string_ref;
+    last_resolution_.entry_string_ref = result->entry_string_ref;
+    last_resolution_.best_config_name = result->config.toString();
+    if (!overlaid) {
+      last_resolution_.best_package_name = result->package_name->c_str();
     }
   }
 
-  return *final_result;
+  return result;
 }
 
 base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -821,10 +778,8 @@
   // If `desired_config` is not the same as the set configuration or the caller will accept a value
   // from any configuration, then we cannot use our filtered list of types since it only it contains
   // types matched to the set configuration.
-  const bool use_filtered = !ignore_configuration && std::find_if(
-      configurations_.begin(), configurations_.end(),
-      [&desired_config](auto& value) { return &desired_config == &value; })
-      != configurations_.end();
+  const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
+
   const size_t package_count = package_group.packages_.size();
   for (size_t pi = 0; pi < package_count; pi++) {
     const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -979,22 +934,10 @@
   }
 
   std::stringstream log_stream;
-  if (configurations_.size() == 1) {
-    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
-                                     "\tFor config - %s", resid, resource_name_string.c_str(),
-                                     configurations_[0].toString().c_str());
-  } else {
-    ResTable_config conf = configurations_[0];
-    conf.clearLocale();
-    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
-                                     resid, resource_name_string.c_str(), conf.toString().c_str());
-    char str[40];
-    str[0] = '\0';
-    for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
-      iter->getBcp47Locale(str);
-      log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
-    }
-  }
+  log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+                                   "\tFor config - %s", resid, resource_name_string.c_str(),
+                                   configuration_.toString().c_str());
+
   for (const Resolution::Step& step : last_resolution_.steps) {
     constexpr static std::array kStepStrings = {
         "Found initial",
@@ -1484,14 +1427,11 @@
       package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
         FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
-          for (auto & config : configurations_) {
-            if (type_entry.config.match(config)) {
-              if (!group) {
-                group = &package.filtered_configs_.editItemAt(type_id - 1);
-              }
-              group->type_entries.push_back(&type_entry);
-              break;
+          if (type_entry.config.match(configuration_)) {
+            if (!group) {
+              group = &package.filtered_configs_.editItemAt(type_id - 1);
             }
+            group->type_entries.push_back(&type_entry);
           }
         }
       });
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 06d19e0..5a63612 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,22 +2568,6 @@
     return false;
 }
 
-bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
-        const ResTable_config* requested) const {
-    if (requested) {
-        if (imsi || o.imsi) {
-            if ((mcc != o.mcc) && requested->mcc) {
-                return (mcc);
-            }
-
-            if ((mnc != o.mnc) && requested->mnc) {
-                return (mnc);
-            }
-        }
-    }
-    return false;
-}
-
 bool ResTable_config::isBetterThan(const ResTable_config& o,
         const ResTable_config* requested) const {
     if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index d9ff35b..f611d0d 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
   using ApkAssetsWPtr = wp<const ApkAssets>;
   using ApkAssetsList = std::span<const ApkAssetsPtr>;
 
-  AssetManager2();
+  AssetManager2() = default;
   explicit AssetManager2(AssetManager2&& other) = default;
   AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
 
@@ -156,14 +156,10 @@
 
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
-  void SetConfigurations(std::vector<ResTable_config> configurations);
+  void SetConfiguration(const ResTable_config& configuration);
 
-  inline const std::vector<ResTable_config>& GetConfigurations() const {
-    return configurations_;
-  }
-
-  inline void SetDefaultLocale(uint32_t default_locale) {
-    default_locale_ = default_locale;
+  inline const ResTable_config& GetConfiguration() const {
+    return configuration_;
   }
 
   // Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -469,11 +465,9 @@
   // without taking too much memory.
   std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
 
-  uint32_t default_locale_;
-
-  // The current configurations set for this AssetManager. When this changes, cached resources
+  // The current configuration set for this AssetManager. When this changes, cached resources
   // may need to be purged.
-  std::vector<ResTable_config> configurations_;
+  ResTable_config configuration_ = {};
 
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index fdb3551..6de1d1e 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,8 +1375,6 @@
     // match the requested configuration at all.
     bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
 
-    bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
-
     String8 toString() const;
 };
 
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 2caa98c..6fae72a 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,12 +228,10 @@
 
   ResTable_config config;
   memset(&config, 0, sizeof(config));
-  std::vector<ResTable_config> configs;
-  configs.push_back(config);
 
   while (state.KeepRunning()) {
-    configs[0].sdkVersion = ~configs[0].sdkVersion;
-    assets.SetConfigurations(configs);
+    config.sdkVersion = ~config.sdkVersion;
+    assets.SetConfiguration(config);
   }
 }
 BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index c62f095..df3fa02 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
 TEST_F(AssetManager2Test, DensityOverride) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
-  assetmanager.SetConfigurations({{
+  assetmanager.SetConfiguration({
     .density = ResTable_config::DENSITY_XHIGH,
     .sdkVersion = 21,
-  }});
+  });
 
   auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
   ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
   assetmanager.SetResourceResolutionLoggingEnabled(false);
 
@@ -736,7 +736,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({overlayable_assets_});
 
   const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 8b883f4..b97dd96 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets(apk_assets);
   if (config != nullptr) {
-    assetmanager.SetConfigurations({*config});
+    assetmanager.SetConfiguration(*config);
   }
 
   while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 181d141..e08a6a7 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
   ResTable_config night{};
   night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
   night.version = 8u;
-  am_night.SetConfigurations({night});
+  am_night.SetConfiguration(night);
 
   auto theme = am.NewTheme();
   {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b5e6f94..7f80dff 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -44,7 +44,7 @@
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
         "-DATRACE_TAG=ATRACE_TAG_VIEW",
-        "-DLOG_TAG=\"OpenGLRenderer\"",
+        "-DLOG_TAG=\"HWUI\"",
         "-Wall",
         "-Wthread-safety",
         "-Wno-unused-parameter",
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 #include <log/log.h>
 
 #include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
 #include <sys/cdefs.h>
 #include <vulkan/vulkan.h>
 
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
 
 #include <memory>
 
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 // #define LOG_NDEBUG 0
 #include "Bitmap.h"
 
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
 #include "BitmapFactory.h"
 
 #include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
 #include "BitmapRegionDecoder.h"
 
 #include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index af1668f..0c3af61 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "FontUtils.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
 #include <assert.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
 #include <JankTracker.h>
 #include <log/log.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "9patch"
 #define LOG_NDEBUG 1
 
 #include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d2a4efe..1ba7f70 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,9 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
 #include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..2c13ceb 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
 #include <vector>
 
 #include "Gainmap.h"
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 69418b0..4dbfa88 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkStream.h"
 #include "YuvToJpegEncoder.h"
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index ee22f7c..422ffea 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <FrameInfo.h>
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Interpolator.h>
 #include <cutils/log.h>
 
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Animator.h>
 #include <Interpolator.h>
 #include <RenderProperties.h>
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 8cfdeeb7..2ec94c9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "Font.h"
 #include "SkData.h"
 #include "SkFont.h"
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 1e392b1..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedUtfChars.h>
 
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
 #include <sys/types.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
 
 #include "PdfUtils.h"
 
-#include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
 
 #include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
 
 namespace android {
 
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
 #include <minikin/GraphemeBreak.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..9ebf23c 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
 #include "utils/misc.h"
 #include "utils/Log.h"
 #include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..081713a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
 #include "GraphicsJNI.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8c377b9..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
 namespace uirenderer {
 
 /**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
  */
 struct DrawGlInfo {
     // Input: current clip rect
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index f340945..a6e8c08f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,9 +34,6 @@
 #include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index b0ba619..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -25,9 +25,6 @@
 #include "VulkanManager.h"
 #include "utils/Color.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 0ad8c24..0ff1b1e 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -42,7 +42,9 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.BaseColumns;
 import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.provider.Settings.System;
@@ -543,6 +545,95 @@
         return getUriFromCursor(mContext, mCursor);
     }
 
+    /**
+     * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+     *
+     * @param contentResolver ContentResolver to execute media query.
+     * @param value a canonicalized uri which refers to the ringtone.
+     * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+     *     be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+     *     RingtoneManager.TYPE_ALARM.
+     * @hide
+     */
+    public static @Nullable Uri getRingtoneUriForRestore(
+            @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+            throws FileNotFoundException, IllegalArgumentException {
+        if (value == null) {
+            // Return a valid null. It means the null value is intended instead of a failure.
+            return null;
+        }
+
+        Uri ringtoneUri;
+        final Uri canonicalUri = Uri.parse(value);
+
+        // Try to get the media uri via the regular uncanonicalize method first.
+        ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+        if (ringtoneUri != null) {
+            // Canonicalize it to make the result contain the right metadata of the media asset.
+            ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+            return ringtoneUri;
+        }
+
+        // Query the media by title and ringtone type.
+        final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+        Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+        String ringtoneTypeSelection = "";
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_RINGTONE:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+                break;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+                break;
+            case RingtoneManager.TYPE_ALARM:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+        }
+
+        final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+        Cursor cursor = null;
+        try {
+            cursor =
+                    contentResolver.query(
+                            baseUri,
+                            /* projection */ new String[] {BaseColumns._ID},
+                            /* selection */ selection,
+                            /* selectionArgs */ new String[] {title},
+                            /* sortOrder */ null,
+                            /* cancellationSignal */ null);
+
+        } catch (IllegalArgumentException e) {
+            throw new FileNotFoundException("Volume not found for " + baseUri);
+        }
+        if (cursor == null) {
+            throw new FileNotFoundException("Missing cursor for " + baseUri);
+        } else if (cursor.getCount() == 0) {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("No item found for " + baseUri);
+        } else if (cursor.getCount() > 1) {
+            // Find more than 1 result.
+            // We are not sure which one is the right ringtone file so just abandon this case.
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException(
+                    "Find multiple ringtone candidates by title+ringtone_type query: count: "
+                            + cursor.getCount());
+        }
+        if (cursor.moveToFirst()) {
+            ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+            FileUtils.closeQuietly(cursor);
+        } else {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("Failed to read row from the result.");
+        }
+
+        // Canonicalize it to make the result contain the right metadata of the media asset.
+        ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+        Log.v(TAG, "Find a valid result: " + ringtoneUri);
+        return ringtoneUri;
+    }
+
     private static Uri getUriFromCursor(Context context, Cursor cursor) {
         final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
                 cursor.getLong(ID_COLUMN_INDEX));
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 283445f..b50514d 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
 
 void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
     ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
-    ResTable_config config = locked_mgr->GetConfigurations()[0];
+    ResTable_config config = locked_mgr->GetConfiguration();
 
     // AConfiguration is not a virtual subclass, so we can memcpy.
     memcpy(out, &config, sizeof(config));
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.LoaderManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -714,8 +715,13 @@
 
                     try {
                         mPrinterForInfoIntent = printer;
+                        Bundle options = ActivityOptions.makeBasic()
+                                .setPendingIntentBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle();
                         startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
-                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+                                options);
                     } catch (SendIntentException e) {
                         mPrinterForInfoIntent = null;
                         Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 0d1a1fe..ec60f8c 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -24,4 +24,6 @@
     <string name="single_line_summary_preference_title" translatable="false">Preference (singleLineSummary = true)</string>
     <!-- Summary for single line summary preference. [DO NOT TRANSLATE] -->
     <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
+    <!-- Footer text with two links. [DO NOT TRANSLATE] -->
+    <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
similarity index 92%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 9c7e0ce..50c0eb7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -28,9 +28,11 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.AnnotatedText
 import com.android.settingslib.spa.widget.ui.Footer
 
 private const val TITLE = "Sample Footer"
@@ -78,6 +80,9 @@
                 entry.UiLayout()
             }
             Footer(footerText = "Footer text always at the end of page.")
+            Footer {
+                AnnotatedText(R.string.footer_with_two_links)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 9ddd0c6..88ba4b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -16,105 +16,93 @@
 
 package com.android.settingslib.spa.framework.util
 
-import android.content.res.Resources
 import android.graphics.Typeface
 import android.text.Spanned
 import android.text.style.StyleSpan
 import android.text.style.URLSpan
 import androidx.annotation.StringRes
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.Density
+import androidx.compose.ui.text.style.TextDecoration
 
-const val URLSPAN_TAG = "URLSPAN_TAG"
+const val URL_SPAN_TAG = "URL_SPAN_TAG"
 
 @Composable
-fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
-    LocalConfiguration.current
+fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
     val resources = LocalContext.current.resources
-    val density = LocalDensity.current
+    val urlSpanColor = MaterialTheme.colorScheme.primary
     return remember(id) {
         val text = resources.getText(id)
-        spannableStringToAnnotatedString(text, density, urlSpanColor)
+        spannableStringToAnnotatedString(text, urlSpanColor)
     }
 }
 
-private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
-    return if (text is Spanned) {
-        with(density) {
-            buildAnnotatedString {
-                append((text.toString()))
-                text.getSpans(0, text.length, Any::class.java).forEach {
-                    val start = text.getSpanStart(it)
-                    val end = text.getSpanEnd(it)
-                    when (it) {
-                        is StyleSpan ->
-                            when (it.style) {
-                                Typeface.NORMAL -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Normal,
-                                                fontStyle = FontStyle.Normal
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.BOLD -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Bold,
-                                                fontStyle = FontStyle.Normal
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.ITALIC -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Normal,
-                                                fontStyle = FontStyle.Italic
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.BOLD_ITALIC -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Bold,
-                                                fontStyle = FontStyle.Italic
-                                        ),
-                                        start,
-                                        end
-                                )
-                            }
-                        is URLSpan -> {
-                            addStyle(
-                                    SpanStyle(
-                                            color = urlSpanColor,
-                                    ),
-                                    start,
-                                    end
-                            )
-                            if (!it.url.isNullOrEmpty()) {
-                                addStringAnnotation(
-                                        URLSPAN_TAG,
-                                        it.url,
-                                        start,
-                                        end
-                                )
-                            }
-                        }
-                        else -> addStyle(SpanStyle(), start, end)
-                    }
+private fun spannableStringToAnnotatedString(text: CharSequence, urlSpanColor: Color) =
+    if (text is Spanned) {
+        buildAnnotatedString {
+            append((text.toString()))
+            for (span in text.getSpans(0, text.length, Any::class.java)) {
+                val start = text.getSpanStart(span)
+                val end = text.getSpanEnd(span)
+                when (span) {
+                    is StyleSpan -> addStyleSpan(span, start, end)
+                    is URLSpan -> addUrlSpan(span, urlSpanColor, start, end)
+                    else -> addStyle(SpanStyle(), start, end)
                 }
             }
         }
     } else {
         AnnotatedString(text.toString())
     }
+
+private fun AnnotatedString.Builder.addStyleSpan(styleSpan: StyleSpan, start: Int, end: Int) {
+    when (styleSpan.style) {
+        Typeface.NORMAL -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Normal),
+            start,
+            end,
+        )
+
+        Typeface.BOLD -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+            start,
+            end,
+        )
+
+        Typeface.ITALIC -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Italic),
+            start,
+            end,
+        )
+
+        Typeface.BOLD_ITALIC -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic),
+            start,
+            end,
+        )
+    }
+}
+
+private fun AnnotatedString.Builder.addUrlSpan(
+    urlSpan: URLSpan,
+    urlSpanColor: Color,
+    start: Int,
+    end: Int,
+) {
+    addStyle(
+        SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+        start,
+        end,
+    )
+    if (!urlSpan.url.isNullOrEmpty()) {
+        addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
+    }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
new file mode 100644
index 0000000..82ac7e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalUriHandler
+import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+@Composable
+fun AnnotatedText(@StringRes id: Int) {
+    val uriHandler = LocalUriHandler.current
+    val annotatedString = annotatedStringResource(id)
+    ClickableText(
+        text = annotatedString,
+        style = MaterialTheme.typography.bodyMedium.copy(
+            color = MaterialTheme.colorScheme.onSurfaceVariant,
+        ),
+    ) { offset ->
+        // Gets the url at the clicked position.
+        annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
+            .firstOrNull()
+            ?.let { uriHandler.openUri(it.item) }
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index cbfea06..fb8f878 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -26,5 +26,7 @@
         other {There are # songs found in {place}.}
     }</string>
 
-    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
+    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+
+    <string name="test_link"><a href="https://www.android.com/">link</a></string>
 </resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index b65be42..9928355 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -16,14 +16,14 @@
 
 package com.android.settingslib.spa.framework.util
 
-import androidx.compose.ui.graphics.Color
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.style.TextDecoration
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.util.URLSPAN_TAG
-import com.android.settingslib.spa.framework.util.annotatedStringResource
 import com.android.settingslib.spa.test.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -38,24 +38,34 @@
     @Test
     fun testAnnotatedStringResource() {
         composeTestRule.setContent {
-            val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)
+            val annotatedString =
+                annotatedStringResource(R.string.test_annotated_string_resource)
 
             val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
-            assertThat(annotations).hasSize(1)
-            assertThat(annotations[0].start).isEqualTo(31)
-            assertThat(annotations[0].end).isEqualTo(35)
-            assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
-            assertThat(annotations[0].item).isEqualTo("https://www.google.com/")
+            assertThat(annotations).containsExactly(
+                AnnotatedString.Range(
+                    item = "https://www.android.com/",
+                    start = 31,
+                    end = 35,
+                    tag = URL_SPAN_TAG,
+                )
+            )
 
-            assertThat(annotatedString.spanStyles).hasSize(2)
-            assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
-            assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
-            assertThat(annotatedString.spanStyles[0].item).isEqualTo(
-                    SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))
-
-            assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
-            assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
-            assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
+            assertThat(annotatedString.spanStyles).containsExactly(
+                AnnotatedString.Range(
+                    item = SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+                    start = 22,
+                    end = 26,
+                ),
+                AnnotatedString.Range(
+                    item = SpanStyle(
+                        color = MaterialTheme.colorScheme.primary,
+                        textDecoration = TextDecoration.Underline,
+                    ),
+                    start = 31,
+                    end = 35,
+                ),
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
new file mode 100644
index 0000000..2c218e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.android.settingslib.spa.widget.ui.AnnotatedText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RunWith(AndroidJUnit4::class)
+class AnnotatedTextTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var uriHandler: UriHandler
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun text_isDisplayed() {
+        composeTestRule.setContent {
+            AnnotatedText(R.string.test_annotated_string_resource)
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_annotated_string_resource))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun onUriClick_openUri() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalUriHandler provides uriHandler) {
+                AnnotatedText(R.string.test_link)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_link)).performClick()
+
+        verify(uriHandler).openUri("https://www.android.com/")
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 21c9e34..945f2e2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -28,8 +28,7 @@
     title: String,
     packageName: String,
     userId: Int,
-    footerText: String,
-    footerContent: (@Composable () -> Unit)?,
+    footerContent: @Composable () -> Unit,
     packageManagers: IPackageManagers,
     content: @Composable PackageInfo.() -> Unit,
 ) {
@@ -41,10 +40,6 @@
 
         packageInfo.content()
 
-        if (footerContent != null) {
-            Footer(footerContent)
-        } else {
-            Footer(footerText)
-        }
+        Footer(footerContent)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 7c689c6..7f82be4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -38,6 +38,7 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.ui.AnnotatedText
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -140,8 +141,7 @@
         title = stringResource(pageTitleResId),
         packageName = packageName,
         userId = userId,
-        footerText = stringResource(footerResId),
-        footerContent = footerContent(),
+        footerContent = { AnnotatedText(footerResId) },
         packageManagers = packageManagers,
     ) {
         val model = createSwitchModel(applicationInfo)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index f4b3204..1ab6230 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,7 +20,6 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.ui.text.AnnotatedString
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.rememberContext
@@ -37,10 +36,7 @@
     val footerResId: Int
     val switchRestrictionKeys: List<String>
         get() = emptyList()
-    @Composable
-    fun footerContent(): (@Composable () -> Unit)? {
-        return null
-    }
+
     /**
      * Loads the extra info for the App List, and generates the [AppRecord] List.
      *
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f1efe9e..bbfdc38 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -385,5 +385,6 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
                 BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -332,21 +333,30 @@
      * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
      */
     private void setRingtone(String name, String value) {
-        // If it's null, don't change the default
-        if (value == null) return;
-        final Uri ringtoneUri;
-        if (SILENT_RINGTONE.equals(value)) {
-            ringtoneUri = null;
-        } else {
-            Uri canonicalUri = Uri.parse(value);
-            ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
-            if (ringtoneUri == null) {
-                // Unrecognized or invalid Uri, don't restore
-                return;
-            }
-        }
-        final int ringtoneType = getRingtoneType(name);
+        Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
 
+        // If it's null, don't change the default.
+        if (value == null) return;
+        final int ringtoneType = getRingtoneType(name);
+        if (SILENT_RINGTONE.equals(value)) {
+            // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+            // device.
+            RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+            return;
+        }
+
+        Uri ringtoneUri = null;
+        try {
+            ringtoneUri =
+                    RingtoneManager.getRingtoneUriForRestore(
+                            mContext.getContentResolver(), value, ringtoneType);
+        } catch (FileNotFoundException | IllegalArgumentException e) {
+            Log.w(TAG, "Failed to resolve " + value + ": " + e);
+            // Unrecognized or invalid Uri, don't restore
+            return;
+        }
+
+        Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
         RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
     }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 61afb5b..5475fad 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -858,7 +858,8 @@
                  Settings.Secure.UI_TRANSLATION_ENABLED,
                  Settings.Secure.CREDENTIAL_SERVICE,
                  Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED);
+                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
+                 Settings.Secure.DND_CONFIGS_MIGRATED);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
     private static final String SETTING_VALUE = "setting_value";
     private static final String SETTING_REAL_VALUE = "setting_real_value";
 
+    private static final String DEFAULT_RINGTONE_VALUE =
+            "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+    private static final String DEFAULT_NOTIFICATION_VALUE =
+            "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+    private static final String DEFAULT_ALARM_VALUE =
+            "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
     private SettingsHelper mSettingsHelper;
 
     @Mock private Context mContext;
@@ -74,6 +90,7 @@
                 mTelephonyManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
         when(mContext.getContentResolver()).thenReturn(getContentResolver());
 
         mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
     }
 
     @Test
+    public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+        final String sourceRingtoneValue =
+                "content://media/internal/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://media/internal/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+                        return Uri.parse(newRingtoneValueUncanonicalized);
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/200";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {200L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.NOTIFICATION_SOUND,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(
+                        Settings.System.getString(
+                                mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/300";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {300L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.ALARM_ALERT,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        // This is to mock the case that there are multiple results by querying title +
+        // ringtone_type.
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+        cursor.addRow(new Object[] {110L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_restoreSilentValue() {
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                "_silent",
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(null);
+    }
+
+    public static class MockSettingsProvider extends MockContentProvider {
+        ContentResolver mBaseContentResolver;
+
+        public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+            super(context);
+            this.mBaseContentResolver = baseContentResolver;
+        }
+
+        @Override
+        public Bundle call(String method, String request, Bundle args) {
+            return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+        }
+    }
+
+    @Test
     public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
         when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
                 .thenReturn(new String[]{});
@@ -400,4 +788,20 @@
         Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
         Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
     }
+
+    private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+        Settings.System.putString(
+                contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+        assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(DEFAULT_ALARM_VALUE);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4e1cbc7..43f7aeb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -78,6 +78,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -128,6 +129,7 @@
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
     // the audio service will bring up the volume dialog.
@@ -301,6 +303,10 @@
                     mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
                 }
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+            }
         }
 
         @Override
@@ -419,7 +425,8 @@
             Provider<JavaAdapter> javaAdapter,
             UserInteractor userInteractor,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
-            Provider<SceneInteractor> sceneInteractor
+            Provider<SceneInteractor> sceneInteractor,
+            KeyguardTransitionInteractor keyguardTransitionInteractor
     ) {
         super(view);
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -450,6 +457,7 @@
         mUserInteractor = userInteractor;
         mSceneInteractor = sceneInteractor;
         mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 7519202..e121790 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -42,6 +44,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val userRepository: UserRepository,
     private val keyguardRepository: KeyguardRepository,
+    sceneInteractor: SceneInteractor,
     private val clock: SystemClock,
 ) {
     /**
@@ -93,7 +97,7 @@
                 repository.isUnlocked,
                 authenticationMethod,
             ) { isUnlocked, authenticationMethod ->
-                authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked
+                !authenticationMethod.isSecure || isUnlocked
             }
             .stateIn(
                 scope = applicationScope,
@@ -101,6 +105,44 @@
                 initialValue = true,
             )
 
+    /**
+     * Whether the lockscreen has been dismissed (by any method). This can be false even when the
+     * device is unlocked, e.g. when swipe to unlock is enabled.
+     *
+     * Note:
+     * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
+     * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
+     *   transition occurs).
+     */
+    private val isLockscreenDismissed =
+        sceneInteractor.desiredScene
+            .map { it.key }
+            .filter { currentScene ->
+                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+            }
+            .map { it == SceneKey.Gone }
+            .distinctUntilChanged()
+
+    /**
+     * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
+     * authentication. This returns false whenever the lockscreen has been dismissed.
+     *
+     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+     * UI.
+     */
+    val canSwipeToDismiss =
+        combine(authenticationMethod, isLockscreenDismissed) {
+                authenticationMethod,
+                isLockscreenDismissed ->
+                authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
+                    !isLockscreenDismissed
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
     /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
     val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
index 0992882..585c390 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
@@ -24,11 +24,11 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Singleton;
+
 /**
  * Provides items imported from com.android.internal.
  */
@@ -51,7 +51,7 @@
     /** */
     @Provides
     public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
-        return new NotificationMessagingUtil(context);
+        return new NotificationMessagingUtil(context, null);
     }
 
     /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e77d355..60833a9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -286,6 +286,19 @@
                 teamfood = true
             )
 
+    /**
+     * TODO(b/278086361): Tracking bug
+     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+     *
+     * This flag is under development; some types of unlock may not animate properly if you enable
+     * it.
+     */
+    @JvmField
+    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+            unreleasedFlag("keyguard_wm_state_refactor")
+
     /** Stop running face auth when the display state changes to OFF. */
     // TODO(b/294221702): Tracking bug.
     @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
@@ -363,7 +376,7 @@
 
     // TODO(b/292533677): Tracking Bug
     val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
-        unreleasedFlag("wifi_tracker_lib_for_wifi_icon")
+        unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
 
     // TODO(b/293863612): Tracking Bug
     @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
@@ -372,6 +385,9 @@
     // TODO(b/293585143): Tracking Bug
     val INSTANT_TETHER = unreleasedFlag("instant_tether")
 
+    // TODO(b/294588085): Tracking Bug
+    val WIFI_SECONDARY_NETWORKS = unreleasedFlag("wifi_secondary_networks")
+
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
     val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip")
@@ -750,4 +766,8 @@
     /** Enable the Compose implementation of the Quick Settings footer actions. */
     @JvmField
     val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+    /** Enable the share wifi button in Quick Settings internet dialog. */
+    @JvmField
+    val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 public class KeyguardService extends Service {
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
+    private final FeatureFlags mFlags;
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
     private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
                            KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
                            ScreenOnCoordinator screenOnCoordinator,
                            ShellTransitions shellTransitions,
-                           DisplayTracker displayTracker) {
+                           DisplayTracker displayTracker,
+                           WindowManagerLockscreenVisibilityViewModel
+                                   wmLockscreenVisibilityViewModel,
+                           WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+                           KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+                           KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+                           @Application CoroutineScope scope,
+                           FeatureFlags featureFlags) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
         mScreenOnCoordinator = screenOnCoordinator;
         mShellTransitions = shellTransitions;
         mDisplayTracker = displayTracker;
+        mFlags = featureFlags;
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            WindowManagerLockscreenVisibilityViewBinder.bind(
+                    wmLockscreenVisibilityViewModel,
+                    wmLockscreenVisibilityManager,
+                    scope);
+
+            KeyguardSurfaceBehindViewBinder.bind(
+                    keyguardSurfaceBehindViewModel,
+                    keyguardSurfaceBehindAnimator,
+                    scope);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9a09df4..ff74050 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -403,7 +403,9 @@
      * the device.
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
-        return isNexusLauncherUnderneath() &&
+        // TODO(b/278086361): Refactor in-window animations.
+        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+                isNexusLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
                 !notificationShadeWindowController.isLaunchingActivity &&
@@ -847,54 +849,57 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                val surfaceHeight: Int =
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
-            var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                    (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                    MathUtils.clamp(amount, 0f, 1f))
+                var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+                        MathUtils.clamp(amount, 0f, 1f))
 
-            // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
-            // so don't also scale the window.
-            if (keyguardStateController.isDismissingFromSwipe &&
-                    willUnlockWithInWindowLauncherAnimations) {
-                scaleFactor = 1f
-            }
-
-            // Translate up from the bottom.
-            surfaceBehindMatrix.setTranslate(
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
-                            surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
-            )
-
-            // Scale up from a point at the center-bottom of the surface.
-            surfaceBehindMatrix.postScale(
-                    scaleFactor,
-                    scaleFactor,
-                    keyguardViewController.viewRootImpl.width / 2f,
-                    surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
-            )
-
-            // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
-            // unable to draw
-            val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
-            if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                    sc?.isValid == true) {
-                with(SurfaceControl.Transaction()) {
-                    setMatrix(sc, surfaceBehindMatrix, tmpFloat)
-                    setCornerRadius(sc, roundedCornerRadius)
-                    setAlpha(sc, animationAlpha)
-                    apply()
+                // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+                // animations, so don't also scale the window.
+                if (keyguardStateController.isDismissingFromSwipe &&
+                        willUnlockWithInWindowLauncherAnimations) {
+                    scaleFactor = 1f
                 }
-            } else {
-                applyParamsToSurface(
-                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                            surfaceBehindRemoteAnimationTarget.leash)
-                            .withMatrix(surfaceBehindMatrix)
-                            .withCornerRadius(roundedCornerRadius)
-                            .withAlpha(animationAlpha)
-                            .build()
+
+                // Translate up from the bottom.
+                surfaceBehindMatrix.setTranslate(
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+                                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
                 )
+
+                // Scale up from a point at the center-bottom of the surface.
+                surfaceBehindMatrix.postScale(
+                        scaleFactor,
+                        scaleFactor,
+                        keyguardViewController.viewRootImpl.width / 2f,
+                        surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+                )
+
+                // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+                // unable to draw
+                val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+                if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc?.isValid == true) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+                        setCornerRadius(sc, roundedCornerRadius)
+                        setAlpha(sc, animationAlpha)
+                        apply()
+                    }
+                } else {
+                    applyParamsToSurface(
+                            SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                                    surfaceBehindRemoteAnimationTarget.leash)
+                                    .withMatrix(surfaceBehindMatrix)
+                                    .withCornerRadius(roundedCornerRadius)
+                                    .withAlpha(animationAlpha)
+                                    .build()
+                    )
+                }
             }
         }
 
@@ -983,10 +988,12 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            keyguardViewController.hide(
-                surfaceBehindRemoteAnimationStartTime,
-                0 /* fadeOutDuration */
-            )
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                keyguardViewController.hide(
+                        surfaceBehindRemoteAnimationStartTime,
+                        0 /* fadeOutDuration */
+                )
+            }
         } else {
             Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
                     "showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f861d5e..fd15853 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1035,12 +1034,19 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+                        transit, apps, wallpapers, nonApps, finishedCallback);
+            }
             Trace.endSection();
         }
 
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+            }
         }
     };
 
@@ -1106,7 +1112,7 @@
 
                         mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
                         mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
-                        mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+                        //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
                         mOccludeByDreamAnimator.addUpdateListener(
                                 animation -> {
                                     SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1336,6 +1342,8 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
+    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
@@ -1379,7 +1387,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1446,8 +1455,9 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mMainDispatcher = mainDispatcher;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+        mMainDispatcher = mainDispatcher;
     }
 
     public void userActivity() {
@@ -2685,6 +2695,12 @@
             if (DEBUG) {
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager if flag is enabled.
+                return;
+            }
+
             try {
                 mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
@@ -2724,7 +2740,11 @@
             }
             mHiding = false;
 
-            mKeyguardViewControllerLazy.get().show(options);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled directly in StatusBarKeyguardViewManager if enabled.
+                mKeyguardViewControllerLazy.get().show(options);
+            }
+
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
             adjustStatusBarLocked();
@@ -2795,19 +2815,22 @@
             mUpdateMonitor.setKeyguardGoingAway(true);
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
-            // Don't actually hide the Keyguard at the moment, wait for window
-            // manager until it tells us it's safe to do so with
-            // startKeyguardExitAnimation.
-            // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
-            // order.
-            final int keyguardFlag = flags;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error while calling WindowManager", e);
-                }
-            });
+            // Handled in WmLockscreenVisibilityManager if flag is enabled.
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Don't actually hide the Keyguard at the moment, wait for window manager 
+                    // until it tells us it's safe to do so with startKeyguardExitAnimation.
+		    // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager 
+		    // will be in order.
+		    final int keyguardFlag = flags;
+		    mUiBgExecutor.execute(() -> {
+		        try {
+		            mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
+		        } catch (RemoteException e) {
+		            Log.e(TAG, "Error while calling WindowManager", e);
+		        }
+		    });
+            }
+
             Trace.endSection();
         }
     };
@@ -2919,7 +2942,10 @@
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
-                if (finishedCallback != null) {
+                // If the flag is enabled, remote animation state is handled in
+                // WmLockscreenVisibilityManager.
+                if (finishedCallback != null
+                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -2945,10 +2971,12 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                try {
-                                    finishedCallback.onAnimationFinished();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    try {
+                                        finishedCallback.onAnimationFinished();
+                                    } catch (RemoteException e) {
+                                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                    }
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2975,7 +3003,11 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+                    // short circuit when this is null.
+                    mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                }
                 mSurfaceBehindRemoteAnimationRunning = true;
 
                 mInteractionJankMonitor.begin(
@@ -2995,7 +3027,10 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled directly in StatusBarKeyguardViewManager if enabled.
+                    mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                }
 
                 // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
                 // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3009,13 +3044,17 @@
                     }
                     if (apps == null || apps.length == 0) {
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
                         try {
-                            finishedCallback.onAnimationFinished();
+                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                finishedCallback.onAnimationFinished();
+                            }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "RemoteException");
                         } finally {
                             mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         }
+
                         return;
                     }
 
@@ -3039,7 +3078,9 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3050,7 +3091,9 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3199,7 +3242,11 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            mActivityTaskManagerService.keyguardGoingAway(flags);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager.
+                mActivityTaskManagerService.keyguardGoingAway(flags);
+            }
+
             mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val activityTaskManagerService: IActivityTaskManager,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+    /**
+     * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+     * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+     */
+    private var isLockscreenShowing = true
+
+    /**
+     * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+     * to show AOD when the lockscreen is visible.
+     */
+    private var isAodVisible = false
+
+    /**
+     * Whether the keyguard is currently "going away", which we triggered via a call to
+     * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+     * the app/launcher surface behind the keyguard is made visible, and WM calls
+     * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+     * it.
+     *
+     * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+     * do that ourselves once we are done animating the surface.
+     *
+     * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+     * and we have gotten into trouble using it to mean various different things in the past. Unlock
+     * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+     * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+     * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+     * is still 'going away' because unlock animations are playing, it's actually not.
+     *
+     * If you want to know if the keyguard is 'going away', you probably want to check if we have
+     * STARTED but not FINISHED a transition to GONE.
+     *
+     * The going away animation will run until:
+     * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+     * - We call [setLockscreenShown] = true, which cancels the going away animation.
+     * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+     *   second timeout).
+     */
+    private var isKeyguardGoingAway = false
+        private set(value) {
+            // TODO(b/278086361): Extricate the keyguard state controller.
+            keyguardStateController.notifyKeyguardGoingAway(value)
+            field = value
+        }
+
+    /** Callback provided by WM to call once we're done with the going away animation. */
+    private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+     * Manager to effect the change.
+     */
+    fun setSurfaceBehindVisibility(visible: Boolean) {
+        if (isKeyguardGoingAway == visible) {
+            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+            return
+        }
+
+        // The surface behind is always visible if the lockscreen is not showing, so we're already
+        // visible.
+        if (visible && !isLockscreenShowing) {
+            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            return
+        }
+
+        if (visible) {
+            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // lockscreen is still showing as well, allowing us to animate unlocked.
+            Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+            activityTaskManagerService.keyguardGoingAway(0)
+            isKeyguardGoingAway = true
+        } else {
+            // Hide the surface by setting the lockscreen showing.
+            setLockscreenShown(true)
+        }
+    }
+
+    fun setAodVisible(aodVisible: Boolean) {
+        setWmLockscreenState(aodVisible = aodVisible)
+    }
+
+    /** Sets the visibility of the lockscreen. */
+    fun setLockscreenShown(lockscreenShown: Boolean) {
+        setWmLockscreenState(lockscreenShowing = lockscreenShown)
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationStart(
+        @WindowManager.TransitionOldType transit: Int,
+        apps: Array<RemoteAnimationTarget>,
+        wallpapers: Array<RemoteAnimationTarget>,
+        nonApps: Array<RemoteAnimationTarget>,
+        finishedCallback: IRemoteAnimationFinishedCallback
+    ) {
+        goingAwayRemoteAnimationFinishedCallback = finishedCallback
+        keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+        // If WM cancelled the animation, we need to end immediately even if we're still using the
+        // animation.
+        endKeyguardGoingAwayAnimation()
+    }
+
+    /**
+     * Whether the going away remote animation target is in-use, which means we're animating it or
+     * intend to animate it.
+     *
+     * Some unlock animations (such as the translation spring animation) are non-deterministic and
+     * might end after the transition to GONE ends. In that case, we want to keep the remote
+     * animation running until the spring ends.
+     */
+    fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+        if (!usingTarget) {
+            endKeyguardGoingAwayAnimation()
+        }
+    }
+
+    private fun setWmLockscreenState(
+        lockscreenShowing: Boolean = this.isLockscreenShowing,
+        aodVisible: Boolean = this.isAodVisible
+    ) {
+        Log.d(
+            TAG,
+            "#setWmLockscreenState(" +
+                "isLockscreenShowing=$lockscreenShowing, " +
+                "aodVisible=$aodVisible)."
+        )
+
+        if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            return
+        }
+
+        activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+        this.isLockscreenShowing = lockscreenShowing
+        this.isAodVisible = aodVisible
+    }
+
+    private fun endKeyguardGoingAwayAnimation() {
+        if (!isKeyguardGoingAway) {
+            Log.d(
+                TAG,
+                "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+                    "Short-circuiting."
+            )
+            return
+        }
+
+        executor.execute {
+            Log.d(TAG, "Finishing remote animation.")
+            goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+            goingAwayRemoteAnimationFinishedCallback = null
+
+            isKeyguardGoingAway = false
+
+            keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+        }
+    }
+
+    companion object {
+        private val TAG = this::class.java.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index a5ac7c7..9a44230 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -74,12 +75,11 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import java.util.concurrent.Executor;
+
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -146,7 +146,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -189,7 +190,8 @@
                 systemClock,
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
-                systemPropertiesHelper);
+                systemPropertiesHelper,
+                wmLockscreenVisibilityManager);
     }
 
     /** */
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 30f8f3e..12c309c5 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
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlags,
     facePropertyRepository: FacePropertyRepository,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
         // starts going to sleep.
         merge(
                 keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
-                keyguardRepository.isKeyguardGoingAway,
-                userRepository.userSwitchingInProgress
+                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+                } else {
+                    keyguardRepository.isKeyguardGoingAway
+                },
+                userRepository.userSwitchingInProgress,
             )
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index e35c369..42cd3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -99,7 +99,16 @@
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
 
-    /** Observable for the signal that keyguard is about to go away. */
+    /**
+     * Observable for the signal that keyguard is about to go away.
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+            "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+            "whenever possible."
+    )
     val isKeyguardGoingAway: Flow<Boolean>
 
     /** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@
 
                 awaitClose { keyguardStateController.removeCallback(callback) }
             }
+            .distinctUntilChanged()
             .stateIn(
-                scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = keyguardStateController.isUnlocked,
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = false,
             )
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 246ee33..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,6 +32,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    fun keyguardSurfaceBehindRepository(
+        impl: KeyguardSurfaceBehindRepositoryImpl
+    ): KeyguardSurfaceBehindRepository
+
+    @Binds
     fun keyguardTransitionRepository(
         impl: KeyguardTransitionRepositoryImpl
     ): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+    /** Whether we're running animations on the surface. */
+    val isAnimatingSurface: Flow<Boolean>
+
+    /** Set whether we're running animations on the surface. */
+    fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,6 +52,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
 ) :
     TransitionInteractor(
@@ -53,6 +61,7 @@
 
     override fun start() {
         listenForLockscreenToGone()
+        listenForLockscreenToGoneDragging()
         listenForLockscreenToOccluded()
         listenForLockscreenToCamera()
         listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
         listenForLockscreenToAlternateBouncer()
     }
 
+    /**
+     * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+     * or null if we don't care and should just use a reasonable default.
+     *
+     * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+     * LOCKSCREEN is running.
+     */
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .map { startedStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // LOCKSCREEN to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@map null
+                }
+
+                true // TODO(b/278086361): Implement continuous swipe to unlock.
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+     * care and should use a reasonable default.
+     */
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+            ) { startedStep, fromLockscreenStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+                    // animation).
+                    return@combine null
+                } else if (fromLockscreenStep.value > 0.5f) {
+                    // Start the animation once we're 50% transitioned to GONE.
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
         scope.launch {
@@ -169,7 +235,8 @@
                             }
 
                             // If canceled, just put the state back
-                            // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+                            // TODO(b/278086361): This logic should happen in
+                            //  FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
                                 transitionRepository.startTransition(
                                     TransitionInfo(
@@ -201,7 +268,32 @@
         }
     }
 
+    fun dismissKeyguard() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForLockscreenToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGoneDragging() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
     }
 
     companion object {
-        private val DEFAULT_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -44,6 +49,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
 ) :
     TransitionInteractor(
@@ -57,6 +63,57 @@
         listenForPrimaryBouncerToDreamingLockscreenHosted()
     }
 
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    return@combine null
+                }
+
+                fromBouncerStep.value > 0.5f
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // BOUNCER to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@combine null
+                }
+
+                if (fromBouncerStep.value > 0.5f) {
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f,
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    fun dismissPrimaryBouncer() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(
-                    combine(
-                        keyguardInteractor.isActiveDreamLockscreenHosted,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect {
-                    (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
-                    if (
-                        !isBouncerShowing &&
-                            isActiveDreamLockscreenHosted &&
-                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
-                    ) {
-                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                    .sample(
+                            combine(
+                                    keyguardInteractor.isActiveDreamLockscreenHosted,
+                                    transitionInteractor.startedKeyguardTransitionStep,
+                                    ::Pair
+                            ),
+                            ::toTriple
+                    )
+                    .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                        if (
+                                !isBouncerShowing &&
+                                isActiveDreamLockscreenHosted &&
+                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                        ) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        }
                     }
-                }
         }
     }
 
     private fun listenForPrimaryBouncerToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            // This is handled in KeyguardSecurityContainerController and
+            // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+            // transition vs. listening to legacy state flags.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
-                            if (securityMode == Password) {
+                            if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
                                 TO_GONE_SHORT_DURATION
                             } else {
                                 TO_GONE_DURATION
@@ -188,7 +251,7 @@
 
     companion object {
         private val DEFAULT_DURATION = 300.milliseconds
-        val TO_GONE_DURATION = 250.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
         val TO_GONE_SHORT_DURATION = 200.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 53d3c07..562c4db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -34,12 +34,12 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
         repository.setAnimateDozingTransitions(animate)
     }
 
+    fun isKeyguardDismissable(): Boolean {
+        return repository.isKeyguardUnlocked.value
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
+
+        fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+            return when (state) {
+                KeyguardState.OFF -> true
+                KeyguardState.DOZING -> true
+                KeyguardState.DREAMING -> true
+                KeyguardState.AOD -> true
+                KeyguardState.ALTERNATE_BOUNCER -> true
+                KeyguardState.PRIMARY_BOUNCER -> true
+                KeyguardState.LOCKSCREEN -> true
+                KeyguardState.GONE -> false
+                KeyguardState.OCCLUDED -> true
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+    private val repository: KeyguardSurfaceBehindRepository,
+    private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val viewParams: Flow<KeyguardSurfaceBehindModel> =
+        transitionInteractor.isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultParams
+                } else {
+                    combine(
+                        transitionSpecificViewParams,
+                        defaultParams,
+                    ) { transitionParams, defaultParams ->
+                        transitionParams ?: defaultParams
+                    }
+                }
+            }
+
+    val isAnimatingSurface = repository.isAnimatingSurface
+
+    private val defaultParams =
+        transitionInteractor.finishedKeyguardState.map { state ->
+            KeyguardSurfaceBehindModel(
+                alpha =
+                    if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+                    else 0f
+            )
+        }
+
+    /**
+     * View params provided by the transition interactor for the most recently STARTED transition.
+     * This is used to run transition-specific animations on the surface.
+     *
+     * If null, there are no transition-specific view params needed for this transition and we will
+     * use a reasonable default.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+        transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+            when (startedStep.from) {
+                KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+                KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+                // Return null for other states, where no transition specific params are needed.
+                else -> flowOf(null)
+            }
+        }
+
+    fun setAnimatingSurface(animating: Boolean) {
+        repository.setAnimatingSurface(animating)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 8c4c7ae..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,17 +17,20 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
 class KeyguardTransitionInteractor
 @Inject
 constructor(
-    private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
+    private val repository: KeyguardTransitionRepository,
+    private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+    private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+    private val fromPrimaryBouncerTransitionInteractor:
+        dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -128,12 +136,11 @@
         repository.transition(PRIMARY_BOUNCER, GONE)
 
     /** OFF->LOCKSCREEN transition information. */
-    val offToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+    val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
 
     /** DOZING->LOCKSCREEN transition information. */
     val dozingToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+        repository.transition(DOZING, LOCKSCREEN)
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition */
+    /** The destination state of the last started transition. */
     val startedKeyguardState: StateFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     /** The last completed [KeyguardState] transition */
     val finishedKeyguardState: StateFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+    /**
+     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+     * it.
+     */
+    val isInTransitionToAnyState =
+            combine(
+                    startedKeyguardTransitionStep,
+                    finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                startedStep.to != finishedState
+            }
+
     /**
      * The amount of transition into or out of the given [KeyguardState].
      *
@@ -187,4 +207,41 @@
                 }
             }
     }
+
+    fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.from == fromState }
+    }
+
+    fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.to == toState }
+    }
+
+    /**
+     * Called to start a transition that will ultimately dismiss the keyguard from the current
+     * state.
+     */
+    fun startDismissKeyguardTransition() {
+        when (startedKeyguardState.value) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            else ->
+                Log.e(
+                    "KeyguardTransitionInteractor",
+                    "We don't know how to dismiss keyguard from state " +
+                        "${startedKeyguardState.value}"
+                )
+        }
+    }
+
+    /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+    fun isInTransitionToState(
+            state: KeyguardState,
+    ): Flow<Boolean> {
+        return combine(
+                startedKeyguardTransitionStep,
+                finishedKeyguardState,
+        ) { startedStep, finishedState ->
+            startedStep.to == state && finishedState != state
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+    surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+    fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+    private val defaultSurfaceBehindVisibility =
+        transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+    /**
+     * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+     * RUNNING transition, or null if the current transition does not require special surface
+     * visibility handling.
+     *
+     * An example of transition-specific visibility is swipe to unlock, where the surface should
+     * only be visible after swiping 20% of the way up the screen, and should become invisible again
+     * if the user swipes back down.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .flatMapLatest { startedStep ->
+                when (startedStep.from) {
+                    KeyguardState.LOCKSCREEN -> {
+                        fromLockscreenInteractor.surfaceBehindVisibility
+                    }
+                    KeyguardState.PRIMARY_BOUNCER -> {
+                        fromBouncerInteractor.surfaceBehindVisibility
+                    }
+                    else -> flowOf(null)
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Surface visibility, which is either determined by the default visibility in the FINISHED
+     * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val surfaceBehindVisibility: Flow<Boolean> =
+        transitionInteractor
+                .isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultSurfaceBehindVisibility
+                } else {
+                    combine(
+                        transitionSpecificSurfaceBehindVisibility,
+                        defaultSurfaceBehindVisibility,
+                    ) { transitionVisibility, defaultVisibility ->
+                        // Defer to the transition-specific visibility since we're RUNNING a
+                        // transition, but fall back to the default visibility if the current
+                        // transition's interactor did not specify a visibility.
+                        transitionVisibility ?: defaultVisibility
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+     * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+     */
+    val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+        combine(
+                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                transitionInteractor.finishedKeyguardState,
+                surfaceBehindInteractor.isAnimatingSurface
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                // We may still be animating the surface after the keyguard is fully GONE, since
+                // some animations (like the translation spring) are not tied directly to the
+                // transition step amount.
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+     *
+     * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+     * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+     * want to know if the AOD/clock/notifs/etc. are visible.
+     */
+    val lockscreenVisibility: Flow<Boolean> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                // If we finished the transition, use the finished state. If we're running a
+                // transition, use the state we're transitioning FROM. This can be different from
+                // the last finished state if a transition is interrupted. For example, if we were
+                // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+                // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+                // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+                if (finishedState == startedStep.to) finishedState else startedStep.from
+            }
+            .map(::isLockscreenVisible)
+            .distinctUntilChanged()
+
+    /**
+     * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+     * manager's perspective.
+     *
+     * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+     * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+     * clock/smartspace/notif icons are visible.
+     */
+    val aodVisibility: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isDozing,
+                keyguardInteractor.biometricUnlockState,
+            ) { isDozing, biometricUnlockState ->
+                // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+                // directly from AOD to unlocked while dozing).
+                isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+            }
+            .distinctUntilChanged()
+
+    companion object {
+        fun isSurfaceVisible(state: KeyguardState): Boolean {
+            return !isLockscreenVisible(state)
+        }
+
+        fun isLockscreenVisible(state: KeyguardState): Boolean {
+            return state != KeyguardState.GONE
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+    val alpha: Float = 1f,
+
+    /**
+     * If provided, animate from this value to [alpha] unless an animation is already running, in
+     * which case we'll animate from the current value to [alpha].
+     */
+    val animateFromAlpha: Float = alpha,
+    val translationY: Float = 0f,
+
+    /**
+     * If provided, animate from this value to [translationY] unless an animation is already
+     * running, in which case we'll animate from the current value to [translationY].
+     */
+    val animateFromTranslationY: Float = translationY,
+) {
+    fun willAnimateAlpha(): Boolean {
+        return animateFromAlpha != alpha
+    }
+
+    fun willAnimateTranslationY(): Boolean {
+        return animateFromTranslationY != translationY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..a5b00e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val keyguardViewController: KeyguardViewController,
+    private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+    private var surfaceBehind: RemoteAnimationTarget? = null
+    private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+        get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+    private val matrix = Matrix()
+    private val tmpFloat = FloatArray(9)
+
+    private var animatedTranslationY = FloatValueHolder()
+    private val translateYSpring =
+        SpringAnimation(animatedTranslationY).apply {
+            spring =
+                SpringForce().apply {
+                    stiffness = 200f
+                    dampingRatio = 1f
+                }
+            addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+            addEndListener { _, _, _, _ -> 
+                try {
+                    updateIsAnimatingSurface()
+                } catch (e: NullPointerException) {
+                    // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+                    e.printStackTrace()
+                }
+            }
+        }
+
+    private var animatedAlpha = 0f
+    private var alphaAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 500
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener {
+                animatedAlpha = it.animatedValue as Float
+                applyToSurfaceBehind()
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        updateIsAnimatingSurface()
+                    }
+                }
+            )
+        }
+
+    /**
+     * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+     * these will be applied once someone gives us a surface via [applyParamsToSurface].
+     */
+    var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+        set(newParams) {
+            field = newParams
+            startOrUpdateAnimators()
+            applyToSurfaceBehind()
+        }
+
+    /**
+     * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+     * any necessary animations.
+     */
+    fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+        this.surfaceBehind = surface
+        startOrUpdateAnimators()
+    }
+
+    /**
+     * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+     * Attempting to animate a released target will cause a crash.
+     *
+     * This can be called either because we finished animating the surface naturally, or by WM
+     * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+     * latter, cancel any outstanding animations we have.
+     */
+    fun notifySurfaceReleased() {
+        surfaceBehind = null
+
+        if (alphaAnimator.isRunning) {
+            alphaAnimator.cancel()
+        }
+
+        if (translateYSpring.isRunning) {
+            translateYSpring.cancel()
+        }
+    }
+
+    private fun startOrUpdateAnimators() {
+        if (surfaceBehind == null) {
+            return
+        }
+
+        if (viewParams.willAnimateAlpha()) {
+            var fromAlpha = viewParams.animateFromAlpha
+
+            if (alphaAnimator.isRunning) {
+                alphaAnimator.cancel()
+                fromAlpha = animatedAlpha
+            }
+
+            alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+            alphaAnimator.start()
+        }
+
+        if (viewParams.willAnimateTranslationY()) {
+            if (!translateYSpring.isRunning) {
+                // If the spring isn't running yet, set the start value. Otherwise, respect the
+                // current position.
+                animatedTranslationY.value = viewParams.animateFromTranslationY
+            }
+
+            translateYSpring.animateToFinalPosition(viewParams.translationY)
+        }
+
+        updateIsAnimatingSurface()
+    }
+
+    private fun updateIsAnimatingSurface() {
+        interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+    }
+
+    private fun applyToSurfaceBehind() {
+        surfaceBehind?.leash?.let { sc ->
+            executor.execute {
+                if (surfaceBehind == null) {
+                    Log.d(
+                        TAG,
+                        "Attempting to modify params of surface that isn't " +
+                            "animating. Ignoring."
+                    )
+                    matrix.set(Matrix.IDENTITY_MATRIX)
+                    return@execute
+                }
+
+                val translationY =
+                    if (translateYSpring.isRunning) animatedTranslationY.value
+                    else viewParams.translationY
+
+                val alpha =
+                    if (alphaAnimator.isRunning) {
+                        animatedAlpha
+                    } else {
+                        viewParams.alpha
+                    }
+
+                if (
+                    keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc.isValid
+                ) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(
+                            sc,
+                            matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+                            tmpFloat
+                        )
+                        setAlpha(sc, alpha)
+                        apply()
+                    }
+                } else {
+                    surfaceTransactionApplier.scheduleApply(
+                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+                            .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+                            .withAlpha(alpha)
+                            .build()
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: KeyguardSurfaceBehindViewModel,
+        applier: KeyguardSurfaceBehindParamsApplier,
+        scope: CoroutineScope
+    ) {
+        scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: WindowManagerLockscreenVisibilityViewModel,
+        lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+        scope: CoroutineScope
+    ) {
+        scope.launch {
+            viewModel.surfaceBehindVisibility.collect {
+                lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.lockscreenVisibility.collect {
+                lockscreenVisibilityManager.setLockscreenShown(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+        }
+
+        scope.launch {
+            viewModel.surfaceBehindAnimating.collect {
+                lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+    val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index f46d0eb..11e85d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -27,7 +26,6 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -53,14 +51,15 @@
             )
 
     /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: Flow<SceneKey> =
-        authenticationInteractor.authenticationMethod.map { authenticationMethod ->
-            if (authenticationMethod is AuthenticationMethodModel.Swipe) {
-                SceneKey.Gone
-            } else {
-                SceneKey.Bouncer
-            }
-        }
+    val upDestinationSceneKey =
+        authenticationInteractor.canSwipeToDismiss
+            .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue =
+                    upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
+            )
 
     /** Notifies that the lock button on the lock screen was clicked. */
     fun onLockButtonClicked() {
@@ -73,30 +72,24 @@
     }
 
     private fun upDestinationSceneKey(
-        isSwipeToUnlockEnabled: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+        return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer
     }
 
     private fun lockIcon(
         isUnlocked: Boolean,
     ): Icon {
-        return Icon.Resource(
-            res =
-                if (isUnlocked) {
-                    R.drawable.ic_device_lock_off
-                } else {
-                    R.drawable.ic_device_lock_on
-                },
-            contentDescription =
-                ContentDescription.Resource(
-                    res =
-                        if (isUnlocked) {
-                            R.string.accessibility_unlock_button
-                        } else {
-                            R.string.accessibility_lock_icon
-                        }
-                )
-        )
+        return if (isUnlocked) {
+            Icon.Resource(
+                R.drawable.ic_device_lock_off,
+                ContentDescription.Resource(R.string.accessibility_unlock_button)
+            )
+        } else {
+            Icon.Resource(
+                R.drawable.ic_device_lock_on,
+                ContentDescription.Resource(R.string.accessibility_lock_icon)
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+    val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+    val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+    val lockscreenVisibility = interactor.lockscreenVisibility
+    val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index b88eba9..a3d1d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -157,6 +157,7 @@
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
+        private int mLatestUpdateVolume = -1;
 
         MediaDeviceBaseViewHolder(View view) {
             super(view);
@@ -314,7 +315,11 @@
             mSeekBar.setMaxVolume(device.getMaxVolume());
             final int currentVolume = device.getCurrentVolume();
             if (!mIsDragging) {
-                if (mSeekBar.getVolume() != currentVolume) {
+                if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
+                        || currentVolume == mLatestUpdateVolume)) {
+                    // Update only if volume of device and value of volume bar doesn't match.
+                    // Check if response volume match with the latest request, to ignore obsolete
+                    // response
                     if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
                         updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
                                         : R.drawable.media_output_icon_volume,
@@ -330,12 +335,16 @@
                                 updateUnmutedVolumeIcon();
                             }
                             mSeekBar.setVolume(currentVolume);
+                            mLatestUpdateVolume = -1;
                         }
                     }
                 } else if (currentVolume == 0) {
                     mSeekBar.resetVolume();
                     updateMutedVolumeIcon();
                 }
+                if (currentVolume == mLatestUpdateVolume) {
+                    mLatestUpdateVolume = -1;
+                }
             }
             if (mIsInitVolumeFirstTime) {
                 mIsInitVolumeFirstTime = false;
@@ -360,6 +369,7 @@
                         mStartFromMute = false;
                     }
                     if (progressToVolume != deviceVolume) {
+                        mLatestUpdateVolume = progressToVolume;
                         mController.adjustVolume(device, progressToVolume);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 6fc715a..e018397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -170,19 +170,19 @@
         // Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
         // the disappear animation will not animate into a dot but remove the chip entirely
         hasPersistentDot = false
-        // if we are currently showing a persistent dot, hide it
-        if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
-        // if we are currently animating into a dot, wait for the animation to finish and then hide
-        // the dot
-        if (animationState.value == ANIMATING_OUT) {
-            coroutineScope.launch {
-                withTimeout(DISAPPEAR_ANIMATION_DURATION) {
-                    animationState.first {
-                        it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
-                    }
-                    notifyHidePersistentDot()
-                }
+
+        if (animationState.value == SHOWING_PERSISTENT_DOT) {
+            // if we are currently showing a persistent dot, hide it and update the animationState
+            notifyHidePersistentDot()
+            if (scheduledEvent.value != null) {
+                animationState.value = ANIMATION_QUEUED
+            } else {
+                animationState.value = IDLE
             }
+        } else if (animationState.value == ANIMATING_OUT) {
+            // if we are currently animating out, hide the dot. The animationState will be updated
+            // once the animation has ended in the onAnimationEnd callback
+            notifyHidePersistentDot()
         }
     }
 
@@ -366,14 +366,6 @@
         logger?.logHidePersistentDotCallbackInvoked()
         val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
 
-        if (animationState.value == SHOWING_PERSISTENT_DOT) {
-            if (scheduledEvent.value != null) {
-                animationState.value = ANIMATION_QUEUED
-            } else {
-                animationState.value = IDLE
-            }
-        }
-
         if (anims.isNotEmpty()) {
             val aSet = AnimatorSet()
             aSet.playTogether(anims)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
index 4ebf337..ad22c61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
@@ -51,7 +51,7 @@
 object NotifPipelineChoreographerModule
 
 @Module
-private interface PrivateModule {
+interface PrivateModule {
     @Binds
     fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer
 }
@@ -59,7 +59,7 @@
 private const val TIMEOUT_MS: Long = 100
 
 @SysUISingleton
-private class NotifPipelineChoreographerImpl @Inject constructor(
+class NotifPipelineChoreographerImpl @Inject constructor(
     private val viewChoreographer: Choreographer,
     @Main private val executor: DelayableExecutor
 ) : NotifPipelineChoreographer {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index aeeeb4f..9ba1f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -38,7 +38,7 @@
 interface SensitiveContentCoordinatorModule
 
 @Module
-private interface PrivateSensitiveContentCoordinatorModule {
+interface PrivateSensitiveContentCoordinatorModule {
     @Binds
     fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
 }
@@ -47,7 +47,7 @@
 interface SensitiveContentCoordinator : Coordinator
 
 @CoordinatorScope
-private class SensitiveContentCoordinatorImpl @Inject constructor(
+class SensitiveContentCoordinatorImpl @Inject constructor(
     private val dynamicPrivacyController: DynamicPrivacyController,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
index 357c5b2..c00bb93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -50,7 +50,7 @@
 @Module(includes = [
     SensitiveContentCoordinatorModule::class,
 ])
-private abstract class InternalCoordinatorsModule {
+abstract class InternalCoordinatorsModule {
     @Binds
     @Internal
     abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 7b59266..49990d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -38,7 +38,7 @@
 }
 
 @SectionHeaderScope
-internal class SectionHeaderNodeControllerImpl @Inject constructor(
+class SectionHeaderNodeControllerImpl @Inject constructor(
     @NodeLabel override val nodeLabel: String,
     private val layoutInflater: LayoutInflater,
     @HeaderText @StringRes private val headerTextResId: Int,
@@ -103,4 +103,4 @@
     override fun offerToKeepInParentForAnimation(): Boolean = false
     override fun removeFromParentIfKeptForAnimation(): Boolean = false
     override fun resetKeepInParentForAnimation() {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 2a9cfd0..75801a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -145,7 +145,7 @@
 }
 
 @Module
-private abstract class SectionHeaderBindingModule {
+abstract class SectionHeaderBindingModule {
     @Binds abstract fun bindsNodeController(impl: SectionHeaderNodeControllerImpl): NodeController
     @Binds abstract fun bindsSectionHeaderController(
         impl: SectionHeaderNodeControllerImpl
@@ -182,4 +182,4 @@
 
 @Scope
 @Retention(AnnotationRetention.BINARY)
-annotation class SectionHeaderScope
\ No newline at end of file
+annotation class SectionHeaderScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 78225df..b2c32cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -57,7 +57,7 @@
 object KeyguardNotificationVisibilityProviderModule
 
 @Module
-private interface KeyguardNotificationVisibilityProviderImplModule {
+interface KeyguardNotificationVisibilityProviderImplModule {
     @Binds
     fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl):
             KeyguardNotificationVisibilityProvider
@@ -69,7 +69,7 @@
 }
 
 @SysUISingleton
-private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
+class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
     @Main private val handler: Handler,
     private val keyguardStateController: KeyguardStateController,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
 
     private final ActivityIntentHelper mActivityIntentHelper;
 
-    private final NotificationStackScrollLayoutController mStackScrollerController;
+    public final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -97,6 +101,9 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+
+    private FeatureFlags mFlags;
+
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
             }
         }
     };
+    private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            @Main CoroutineDispatcher mainDispatcher,
+            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
+        mFlags = featureFlags;
         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mMainDispatcher = mainDispatcher;
+        mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
     }
 
+    KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    CoroutineDispatcher mMainDispatcher;
+
     @Override
     public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
         }
     }
 
+    private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onUnlockedChanged() {
+                    updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+                }
+            };
+
     private void registerListeners() {
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mShadeViewController.postToView(() ->
+                    collectFlow(
+                        getViewRootImpl().getView(),
+                        combineFlows(
+                                mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+                                mWmLockscreenVisibilityInteractor.get()
+                                        .getUsingKeyguardGoingAwayAnimation(),
+                                (finishedState, animating) ->
+                                        KeyguardInteractor.Companion.isKeyguardVisibleInState(
+                                                finishedState)
+                                                || animating),
+                        this::consumeShowStatusBarKeyguardView));
+        }
+    }
+
+    private void consumeShowStatusBarKeyguardView(boolean show) {
+        if (show != mLastShowing) {
+            if (show) {
+                show(null);
+            } else {
+                hide(0, 0);
+            }
+        }
     }
 
     /** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b29d461..b5b99a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -29,9 +29,18 @@
     /** Observable for the current wifi default status. */
     val isWifiDefault: StateFlow<Boolean>
 
-    /** Observable for the current wifi network. */
+    /** Observable for the current primary wifi network. */
     val wifiNetwork: StateFlow<WifiNetworkModel>
 
+    /**
+     * Observable for secondary wifi networks (if any). Should specifically exclude the primary
+     * network emitted by [wifiNetwork].
+     *
+     * This isn't used by phones/tablets, which only display the primary network, but may be used by
+     * other variants like Car.
+     */
+    val secondaryNetworks: StateFlow<List<WifiNetworkModel>>
+
     /** Observable for the current wifi network activity. */
     val wifiActivity: StateFlow<DataActivityModel>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index e96288a..80091ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -115,6 +115,11 @@
             .flatMapLatest { it.wifiNetwork }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        activeRepo
+            .flatMapLatest { it.secondaryNetworks }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.secondaryNetworks.value)
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         activeRepo
             .flatMapLatest { it.wifiActivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 99b68005..4b19c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -48,6 +48,9 @@
     private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> = _secondaryNetworks
+
     private val _wifiActivity =
         MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 9ed7c6a..36c46a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -43,6 +43,9 @@
 
     override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         MutableStateFlow(ACTIVITY).asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index afd1576..7c7b58d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -56,8 +56,10 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
@@ -216,6 +218,10 @@
                 initialValue = WIFI_NETWORK_DEFAULT,
             )
 
+    // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         WifiRepositoryHelper.createActivityFlow(
             wifiManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index 175563b..d4f40dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -93,7 +93,8 @@
             WifiPickerTrackerInfo(
                 state = WIFI_STATE_DEFAULT,
                 isDefault = false,
-                network = WIFI_NETWORK_DEFAULT,
+                primaryNetwork = WIFI_NETWORK_DEFAULT,
+                secondaryNetworks = emptyList(),
             )
         callbackFlow {
                 val callback =
@@ -102,6 +103,17 @@
                             val connectedEntry = wifiPickerTracker?.connectedWifiEntry
                             logOnWifiEntriesChanged(connectedEntry)
 
+                            val secondaryNetworks =
+                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+                                    val activeNetworks =
+                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                    activeNetworks
+                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                        .map { it.toWifiNetworkModel() }
+                                } else {
+                                    emptyList()
+                                }
+
                             // [WifiPickerTracker.connectedWifiEntry] will return the same instance
                             // but with updated internals. For example, when its validation status
                             // changes from false to true, the same instance is re-used but with the
@@ -112,8 +124,9 @@
                             // into our internal model immediately. [toWifiNetworkModel] always
                             // returns a new instance, so the flow is guaranteed to emit.
                             send(
-                                newNetwork = connectedEntry?.toWifiNetworkModel()
+                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
                                         ?: WIFI_NETWORK_DEFAULT,
+                                newSecondaryNetworks = secondaryNetworks,
                                 newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
                             )
                         }
@@ -131,9 +144,17 @@
                         private fun send(
                             newState: Int = current.state,
                             newIsDefault: Boolean = current.isDefault,
-                            newNetwork: WifiNetworkModel = current.network,
+                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+                            newSecondaryNetworks: List<WifiNetworkModel> =
+                                current.secondaryNetworks,
                         ) {
-                            val new = WifiPickerTrackerInfo(newState, newIsDefault, newNetwork)
+                            val new =
+                                WifiPickerTrackerInfo(
+                                    newState,
+                                    newIsDefault,
+                                    newPrimaryNetwork,
+                                    newSecondaryNetworks,
+                                )
                             current = new
                             trySend(new)
                         }
@@ -170,7 +191,7 @@
 
     override val wifiNetwork: StateFlow<WifiNetworkModel> =
         wifiPickerTrackerInfo
-            .map { it.network }
+            .map { it.primaryNetwork }
             .distinctUntilChanged()
             .logDiffsForTable(
                 wifiTrackerLibTableLogBuffer,
@@ -179,11 +200,32 @@
             )
             .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        wifiPickerTrackerInfo
+            .map { it.secondaryNetworks }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                columnName = "secondaryNetworks",
+                initialValue = emptyList(),
+            )
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    /**
+     * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+     * primary network. Returns an inactive network if it's not primary.
+     */
+    private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+        return if (!this.isPrimaryNetwork) {
+            WIFI_NETWORK_DEFAULT
+        } else {
+            this.toWifiNetworkModel()
+        }
+    }
+
     /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
     private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
-        if (!this.isPrimaryNetwork) {
-            return WIFI_NETWORK_DEFAULT
-        }
         return if (this is MergedCarrierEntry) {
             this.convertCarrierMergedToModel()
         } else {
@@ -291,7 +333,9 @@
         /** True if wifi is currently the default connection and false otherwise. */
         val isDefault: Boolean,
         /** The currently primary wifi network. */
-        val network: WifiNetworkModel,
+        val primaryNetwork: WifiNetworkModel,
+        /** The current secondary network(s), if any. Specifically excludes the primary network. */
+        val secondaryNetworks: List<WifiNetworkModel>
     )
 
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
index d4abc40..d31c001 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
@@ -41,7 +41,7 @@
 }
 
 @Module
-private interface InternalRemoteInputViewModule {
+interface InternalRemoteInputViewModule {
     @Binds
     fun bindController(impl: RemoteInputViewControllerImpl): RemoteInputViewController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+    return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9ba21da..1a98c12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -46,6 +46,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
@@ -144,6 +146,7 @@
     private lateinit var testableResources: TestableResources
     private lateinit var sceneTestUtils: SceneTestUtils
     private lateinit var sceneInteractor: SceneInteractor
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
 
     private lateinit var underTest: KeyguardSecurityContainerController
@@ -182,6 +185,7 @@
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.SCENE_CONTAINER, false)
         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -204,6 +208,9 @@
         whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
         sceneTestUtils = SceneTestUtils(this)
         sceneInteractor = sceneTestUtils.sceneInteractor()
+        keyguardTransitionInteractor =
+            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+                .keyguardTransitionInteractor
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
@@ -236,9 +243,9 @@
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
                 userInteractor,
                 faceAuthAccessibilityDelegate,
-            ) {
-                sceneInteractor
-            }
+                { sceneInteractor },
+                keyguardTransitionInteractor
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
                 mVibrator,
                 mAuthRippleController,
                 mResources,
-                new KeyguardTransitionInteractor(mTransitionRepository,
-                        TestScopeProvider.getTestScope().getBackgroundScope()),
+                KeyguardTransitionInteractorFactory.create(
+                        TestScopeProvider.getTestScope().getBackgroundScope(),
+                                mTransitionRepository).getKeyguardTransitionInteractor(),
                 KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor
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 d848cd4..fc7d20a 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
@@ -27,6 +27,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -46,9 +48,11 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val repository: AuthenticationRepository = utils.authenticationRepository()
+    private val sceneInteractor = utils.sceneInteractor()
     private val underTest =
         utils.authenticationInteractor(
             repository = repository,
+            sceneInteractor = sceneInteractor,
         )
 
     @Test
@@ -75,10 +79,10 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
 
             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
             assertThat(underTest.getAuthenticationMethod())
@@ -91,10 +95,10 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
-            utils.authenticationRepository.setLockscreenEnabled(false)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+            }
 
             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
             assertThat(underTest.getAuthenticationMethod())
@@ -104,51 +108,87 @@
     @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
-            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()
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+                // 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.
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+            }
+
             assertThat(isUnlocked).isTrue()
         }
 
     @Test
-    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
 
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isTrue()
+        }
+
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
+        }
+
+    @Test
+    fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+            switchToScene(SceneKey.Gone)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
         }
 
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(false)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isTrue()
         }
@@ -156,11 +196,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(false)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -168,11 +208,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(true)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -180,11 +220,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(true)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.None
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -221,11 +261,12 @@
     @Test
     fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
             val pin = List(16) { 9 }
-            utils.authenticationRepository.overrideCredential(pin)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(pin)
+            }
+
             assertThat(underTest.authenticate(pin)).isTrue()
         }
 
@@ -308,10 +349,10 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
         testScope.runTest {
             val isThrottled by collectLastValue(underTest.isThrottled)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
@@ -328,10 +369,10 @@
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
@@ -346,10 +387,10 @@
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
@@ -364,10 +405,10 @@
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN,
@@ -382,10 +423,10 @@
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(false)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN,
@@ -505,10 +546,10 @@
     fun hintedPinLength_withoutAutoConfirm_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(false)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
@@ -517,15 +558,15 @@
     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.overrideCredential(
-                buildList {
-                    repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
-                }
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
@@ -534,13 +575,15 @@
     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
-            utils.authenticationRepository.overrideCredential(
-                buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
-            )
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+                    }
+                )
+            }
 
             assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
         }
@@ -549,16 +592,20 @@
     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(
-                DataLayerAuthenticationMethodModel.Pin
-            )
-            utils.authenticationRepository.overrideCredential(
-                buildList {
-                    repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
-                }
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
+
+    private fun switchToScene(sceneKey: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 847d58b..4a79a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -236,7 +236,7 @@
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
-
+        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
 
         DejankUtils.setImmediate(true);
 
@@ -1068,7 +1068,8 @@
                 mSystemClock,
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
-                mSystemPropertiesHelper);
+                mSystemPropertiesHelper,
+                () -> mock(WindowManagerLockscreenVisibilityManager.class));
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
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 85ee0e4..04ebbf6 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
@@ -52,6 +52,7 @@
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(FACE_AUTH_REFACTOR, true)
+                set(KEYGUARD_WM_STATE_REFACTOR, false)
+            }
         val withDeps =
             KeyguardInteractorFactory.create(
                 featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5e3376a..5ead16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -193,7 +194,7 @@
             assertThat(underTest.isKeyguardShowing()).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isShowing).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isOccluded).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
             assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
             captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+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.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardSurfaceBehindRepositoryImpl()
+    }
+
+    @Test
+    fun testSetAnimatingSurface() {
+        testScope.runTest {
+            val values by collectValues(underTest.isAnimatingSurface)
+
+            runCurrent()
+            underTest.setAnimatingSurface(true)
+            runCurrent()
+            underTest.setAnimatingSurface(false)
+            runCurrent()
+
+            // Default (first) value should be false.
+            assertThat(values).isEqualTo(listOf(false, true, false))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromLockscreenTransitionInteractor
+
+    // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+    // interactor is provided to any classes that need it.
+    override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromLockscreenTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                shadeRepository = FakeShadeRepository(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+            runCurrent()
+
+            assertEquals(
+                values,
+                listOf(
+                    null, // We should start null ("don't care").
+                )
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+    // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+    // underTest interactor is provided to any classes that need it.
+    override var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromPrimaryBouncerTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                keyguardSecurityModel = mock(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false, // Surface is only made visible once the bouncer UI animates out.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false,
+                    true, // Surface should eventually be visible.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardSurfaceBehindInteractor
+    private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+    private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+    private val testScope = TestScope()
+
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
+
+        repository = FakeKeyguardSurfaceBehindRepository()
+        underTest =
+            KeyguardSurfaceBehindInteractor(
+                repository = repository,
+                fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+                transitionInteractor = transitionInteractor,
+            )
+    }
+
+    @Test
+    fun viewParamsSwitchToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+
+            // Start on the LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            // We're on LOCKSCREEN; we should be using the default params.
+            assertEquals(1, values.size)
+            assertTrue(values[0].alpha == 0f)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+            // surface behind model.
+            assertEquals(2, values.size)
+            assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+            // surface behind model.
+            assertEquals(3, values.size)
+            assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+            // alpha=1f when we're GONE.
+            assertEquals(4, values.size)
+            assertEquals(1f, values[3].alpha)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    lateinit var keyguardRepository: FakeKeyguardRepository
+    lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    lateinit var keyguardInteractor: KeyguardInteractor
+    lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    /**
+     * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+     * instance of the interactor for the test.
+     */
+    open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        null
+    open var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        null
+
+    open fun setUp() {
+        keyguardRepository = FakeKeyguardRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        keyguardInteractor =
+            KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                    scope = testScope.backgroundScope,
+                    fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+                            ?: Lazy { mock() },
+                    fromPrimaryBouncerTransitionInteractor =
+                        fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+                )
+                .also {
+                    fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+                    fromPrimaryBouncerTransitionInteractorLazy =
+                        it.fromPrimaryBouncerTransitionInteractor
+                }
+                .keyguardTransitionInteractor
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index aa6bd4e..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -104,12 +104,21 @@
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+            }
 
         transitionInteractor =
             KeyguardTransitionInteractorFactory.create(
                     scope = testScope,
                     repository = transitionRepository,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+                    fromPrimaryBouncerTransitionInteractor = {
+                        fromPrimaryBouncerTransitionInteractor
+                    },
                 )
                 .keyguardTransitionInteractor
 
@@ -119,6 +128,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     shadeRepository = shadeRepository,
                 )
                 .apply { start() }
@@ -129,6 +139,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                 )
                 .apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+    @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+    private val testScope = TestScope()
+
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(lockscreenSurfaceVisibilityFlow)
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+        whenever(surfaceBehindInteractor.isAnimatingSurface)
+            .thenReturn(surfaceBehindIsAnimatingFlow)
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .also { keyguardInteractor = it.keyguardInteractor }
+                .keyguardTransitionInteractor
+
+        underTest =
+            WindowManagerLockscreenVisibilityInteractor(
+                keyguardInteractor = keyguardInteractor,
+                transitionInteractor = transitionInteractor,
+                surfaceBehindInteractor = surfaceBehindInteractor,
+                fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor,
+            )
+    }
+
+    @Test
+    fun surfaceBehindVisibility_switchesToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // We should start with the surface invisible on LOCKSCREEN.
+                ),
+                values
+            )
+
+            val lockscreenSpecificSurfaceVisibility = true
+            lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+            // lockscreenSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                ),
+                values
+            )
+
+            // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false, // FINISHED (LOCKSCREEN)
+                ),
+                values
+            )
+
+            val bouncerSpecificVisibility = true
+            primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+            // the
+            // primaryBouncerSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false,
+                    bouncerSpecificVisibility,
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // Still true when we're FINISHED -> GONE, since we're still animating.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // False once the animation ends.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // We're happily animating while transitioning to gone.
+                ),
+                values
+            )
+
+            // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // Despite the animator still running, this should be false.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // The animator ending should have no effect.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun lockscreenVisibility_visibleWhenGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    // LOCKSCREEN.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false, // Once we're fully GONE, the lockscreen should not be visible.
+                ),
+                values
+            )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+    @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+    @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+    private var isAnimatingSurface: Boolean? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        underTest =
+            KeyguardSurfaceBehindParamsApplier(
+                executor = executor,
+                keyguardViewController = keyguardViewController,
+                interactor = interactor,
+            )
+
+        doAnswer {
+                (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+            }
+            .whenever(interactor)
+            .setAnimatingSurface(anyBoolean())
+    }
+
+    @After
+    fun tearDown() {
+        animatorTestRule.advanceTimeBy(1000.toLong())
+    }
+
+    @Test
+    fun testNotAnimating_setParamsWithNoAnimation() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                alpha = 0.3f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now explicitly not be animating the surface.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_paramsThenSurfaceProvided() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_surfaceThenParamsProvided() {
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // The default params (which do not animate) should have been applied, so we're explicitly
+        // NOT animating yet.
+        assertFalse(checkNotNull(isAnimatingSurface))
+
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_thenReleased_animatingIsFalse() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        assertTrue(checkNotNull(isAnimatingSurface))
+
+        underTest.notifySurfaceReleased()
+
+        // Releasing the surface should immediately cancel animators.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+    private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+
+        underTest =
+            WindowManagerLockscreenVisibilityManager(
+                executor = executor,
+                activityTaskManagerService = activityTaskManagerService,
+                keyguardStateController = keyguardStateController,
+                keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+            )
+    }
+
+    @Test
+    fun testLockscreenVisible_andAodVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+        underTest.setLockscreenShown(false)
+        underTest.setAodVisible(false)
+
+        verify(activityTaskManagerService).setLockScreenShown(false, false)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 45d7a5e..23f243c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -84,22 +84,24 @@
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockEnabled_gone() =
+    fun upTransitionSceneKey_canSwipeToUnlock_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
             utils.authenticationRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setUnlocked(true)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockNotEnabled_bouncer() =
+    fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
         bouncerRepository = FakeKeyguardBouncerRepository()
         transitionRepository = FakeKeyguardTransitionRepository()
         shadeRepository = FakeShadeRepository()
-        val transitionInteractor =
-            KeyguardTransitionInteractor(
-                transitionRepository,
-                testScope.backgroundScope,
-            )
         val keyguardInteractor =
             KeyguardInteractorFactory.create(
+                    repository = keyguardRepository,
                     featureFlags = featureFlags,
                 )
                 .keyguardInteractor
+
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             FingerprintViewModel(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
                 bouncerRepository = it.bouncerRepository
             }
 
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             UdfpsLockscreenViewModel(
                 context,
                 lockscreenColorResId,
                 alternateBouncerResId,
-                KeyguardTransitionInteractor(
-                    transitionRepository,
-                    testScope.backgroundScope,
-                ),
+                transitionInteractor,
                 UdfpsKeyguardInteractor(
                     configRepository,
                     BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5b8272b0..ae0a334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -152,7 +153,11 @@
                 debugLogger,
                 mediaFlags,
                 keyguardUpdateMonitor,
-                KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScopeProvider.getTestScope().backgroundScope,
+                        repository = transitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalSettings
             )
         verify(configurationController).addCallback(capture(configListener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 414256f..6be2fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -400,15 +400,16 @@
 
         // remove persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
+
+        // verify that the onHidePersistentDot callback is invoked
+        verify(listener, times(1)).onHidePersistentDot()
 
         // skip disappear animation
         animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
         testScheduler.runCurrent()
 
-        // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+        // verify that animationState changes to IDLE
         assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
-        verify(listener, times(1)).onHidePersistentDot()
     }
 
     @Test
@@ -473,7 +474,6 @@
 
         // request removal of persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
 
         // schedule another high priority event while the event is animating out
         createAndScheduleFakePrivacyEvent()
@@ -489,6 +489,42 @@
         verify(listener, times(1)).onHidePersistentDot()
     }
 
+    @Test
+    fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+        fastForwardAnimationToState(ANIMATING_OUT)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // request removal of persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+
+        // verify that the state is still ANIMATING_OUT
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation duration
+        testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+        // In an old implementation this would trigger a coroutine timeout causing the
+        // onHidePersistentDot callback to be missed.
+        testScheduler.runCurrent()
+
+        // advance animator time to invoke onAnimationEnd callback
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        testScheduler.runCurrent()
+
+        // verify that onHidePersistentDot is invoked despite the animator callback being delayed
+        // (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested)
+        verify(listener, times(1)).onHidePersistentDot()
+        // verify that animationState is IDLE
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+    }
+
     private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
         // this function should only be called directly after posting a status event
         assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ed9cf3f..0da7360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -701,7 +708,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 1c8dac1..4f7bb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -34,6 +34,8 @@
         MutableStateFlow(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 2dbeb7a..bea1154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -1004,6 +1004,27 @@
         }
 
     @Test
+    fun secondaryNetworks_alwaysEmpty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.secondaryNetworks)
+            collectLastValue(underTest.wifiNetwork)
+
+            // Even WHEN we do have non-primary wifi info
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.isPrimary).thenReturn(false)
+                }
+            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+            // THEN the secondary networks list is empty because this repo doesn't support it
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
     fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index 9959e00..662e36a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -101,6 +101,7 @@
     @Before
     fun setUp() {
         featureFlags.set(Flags.INSTANT_TETHER, false)
+        featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
         whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
             .thenReturn(wifiPickerTracker)
     }
@@ -763,6 +764,197 @@
         }
 
     @Test
+    fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf())
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun secondaryNetworks_oneActiveEntry_hasOne() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+        }
+
+    @Test
+    fun secondaryNetworks_multipleActiveEntries_hasMultiple() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry1 = mock<WifiEntry>()
+            val wifiEntry2 = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry1, wifiEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToInactive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val inactiveEntry =
+                mock<WifiEntry>().apply { whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(inactiveEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToActive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(activeEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToCarrierMerged() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsMultipleInOrder() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(activeEntry, carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat(latest!![1]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![1] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutConnectedEntry() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val connectedEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(1) }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(connectedEntry, secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(connectedEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_noConnectedEntry_hasAllActiveEntries() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutPrimaryNetwork() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val primaryEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, primaryEntry, secondaryEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_flagOff_noNetworks() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
     fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -30,18 +32,36 @@
     fun create(
         scope: CoroutineScope,
         repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+        keyguardInteractor: KeyguardInteractor =
+            KeyguardInteractorFactory.create().keyguardInteractor,
+        fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+            mock<FromLockscreenTransitionInteractor>()
+        },
+        fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+            Lazy {
+                mock<FromPrimaryBouncerTransitionInteractor>()
+            },
     ): WithDependencies {
         return WithDependencies(
             repository = repository,
+            keyguardInteractor = keyguardInteractor,
+            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             KeyguardTransitionInteractor(
                 scope = scope,
                 repository = repository,
+                keyguardInteractor = { keyguardInteractor },
+                fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             )
         )
     }
 
     data class WithDependencies(
         val repository: KeyguardTransitionRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+        val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
         val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 507267e..0829f31 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -134,6 +134,7 @@
 
     fun authenticationInteractor(
         repository: AuthenticationRepository,
+        sceneInteractor: SceneInteractor = sceneInteractor(),
     ): AuthenticationInteractor {
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
@@ -141,6 +142,7 @@
             backgroundDispatcher = testDispatcher,
             userRepository = userRepository,
             keyguardRepository = keyguardRepository,
+            sceneInteractor = sceneInteractor,
             clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e80cba7..996c68b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1026,6 +1026,17 @@
         }
 
         @Override
+        public byte[] getBackupPayload(int userId) {
+            // TODO(b/286124853): back up CDM data
+            return new byte[0];
+        }
+
+        @Override
+        public void applyRestoredPayload(byte[] payload, int userId) {
+            // TODO(b/286124853): restore CDM data
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index d862a54..253ef43 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -33,16 +33,27 @@
       ]
     },
     {
-      "name": "CtsVirtualDevicesTestCases",
+      "name": "CtsHardwareTestCases",
       "options": [
         {
           "include-filter": "android.hardware.input.cts.tests"
         },
         {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ],
       "file_patterns": ["Virtual[^/]*\\.java"]
+    },
+    {
+      "name": "CtsAccessibilityServiceTestCases",
+      "options": [
+        {
+          "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b61e623..ee6c28e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -107,6 +107,7 @@
 
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
             DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
@@ -283,10 +284,6 @@
         if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
         }
-        if (mParams.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_RECENTS)
-                == VirtualDeviceParams.DEVICE_POLICY_CUSTOM) {
-            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
-        }
         return flags;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3fd7d7e..c20f0aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1032,7 +1032,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b5d5cbe..de4979a 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -61,6 +61,7 @@
     private static final String PEOPLE_HELPER = "people";
     private static final String APP_LOCALES_HELPER = "app_locales";
     private static final String APP_GENDER_HELPER = "app_gender";
+    private static final String COMPANION_HELPER = "companion";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -95,7 +96,8 @@
                     PERMISSION_HELPER,
                     NOTIFICATION_HELPER,
                     SYNC_SETTINGS_HELPER,
-                    APP_LOCALES_HELPER);
+                    APP_LOCALES_HELPER,
+                    COMPANION_HELPER);
 
     /** Helpers that are enabled for full, non-system users. */
     private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -132,6 +134,7 @@
         addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
         addHelperIfEligibleForUser(APP_GENDER_HELPER,
                 new AppGrammaticalGenderBackupHelper(mUserId));
+        addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 7ae7d5c..53c0217 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -222,6 +222,21 @@
         );
     }
 
+    /**
+     * Writes a HdmiSoundbarModeStatusReported atom representing a Dynamic soundbar mode status
+     * change.
+     * @param isSupported         Whether the hardware supports ARC.
+     * @param isEnabled           Whether DSM is enabled.
+     * @param enumLogReason       The event that triggered the log.
+     */
+    public void dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.HDMI_SOUNDBAR_MODE_STATUS_REPORTED,
+                isSupported,
+                isEnabled,
+                enumLogReason);
+    }
+
     private int earcStateToEnum(int earcState) {
         switch (earcState) {
             case HDMI_EARC_STATUS_IDLE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dd45307..0d7a6f9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1064,13 +1064,18 @@
      */
     @VisibleForTesting
     public void setSoundbarMode(final int settingValue) {
+        boolean isArcSupported = isArcSupported();
         HdmiCecLocalDevicePlayback playback = playback();
         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+        getAtomWriter().dsmStatusChanged(isArcSupported,
+                settingValue == SOUNDBAR_MODE_ENABLED,
+                HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED);
+
         if (playback == null) {
             Slog.w(TAG, "Device type not compatible to change soundbar mode.");
             return;
         }
-        if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+        if (!isArcSupported) {
             Slog.w(TAG, "Device type doesn't support ARC.");
             return;
         }
@@ -1269,11 +1274,8 @@
     @ServiceThreadOnly
     private List<Integer> getCecLocalDeviceTypes() {
         ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
-        if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
-                == SOUNDBAR_MODE_ENABLED
-                && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
-                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
-                && mSoundbarModeFeatureFlagEnabled) {
+        if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+                && isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
             allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         }
         return allLocalDeviceTypes;
@@ -3589,6 +3591,16 @@
         }
     }
 
+    private boolean isDsmEnabled() {
+        return mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                == SOUNDBAR_MODE_ENABLED;
+    }
+
+    @VisibleForTesting
+    protected boolean isArcSupported() {
+        return SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
+    }
+
     @ServiceThreadOnly
     int getPowerStatus() {
         assertRunOnServiceThread();
@@ -3705,6 +3717,9 @@
             int earcStatus = getEarcStatus();
             getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
                     earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE);
+        } else if (isPlaybackDevice()) {
+            getAtomWriter().dsmStatusChanged(isArcSupported(), isDsmEnabled(),
+                    HdmiStatsEnums.LOG_REASON_DSM_WAKE);
         }
         // TODO: Initialize MHL local devices.
     }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index bfc4f53..2da1a68 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -441,6 +441,58 @@
         return info == null ? null : (IConditionProvider) info.service;
     }
 
+    void resetDefaultFromConfig() {
+        synchronized (mDefaultsLock) {
+            mDefaultComponents.clear();
+            mDefaultPackages.clear();
+        }
+        loadDefaultsFromConfig();
+    }
+
+    boolean removeDefaultFromConfig(int userId) {
+        boolean removed = false;
+        String defaultDndDenied = mContext.getResources().getString(
+                R.string.config_defaultDndDeniedPackages);
+        if (defaultDndDenied != null) {
+            String[] dnds = defaultDndDenied.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < dnds.length; i++) {
+                if (TextUtils.isEmpty(dnds[i])) {
+                    continue;
+                }
+                removed |= removePackageFromApprovedLists(userId, dnds[i], "remove from config");
+            }
+        }
+        return removed;
+    }
+
+    private boolean removePackageFromApprovedLists(int userId, String pkg, String reason) {
+        boolean removed = false;
+        synchronized (mApproved) {
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(
+                    userId);
+            if (approvedByType != null) {
+                int approvedByTypeSize = approvedByType.size();
+                for (int i = 0; i < approvedByTypeSize; i++) {
+                    final ArraySet<String> approved = approvedByType.valueAt(i);
+                    int approvedSize = approved.size();
+                    for (int j = approvedSize - 1; j >= 0; j--) {
+                        final String packageOrComponent = approved.valueAt(j);
+                        final String packageName = getPackageName(packageOrComponent);
+                        if (TextUtils.equals(pkg, packageName)) {
+                            approved.removeAt(j);
+                            removed = true;
+                            if (DEBUG) {
+                                Slog.v(TAG, "Removing " + packageOrComponent
+                                        + " from approved list; " + reason);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return removed;
+    }
+
     private static class ConditionRecord {
         public final Uri id;
         public final ComponentName component;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 8992878..0e76dfb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,6 +25,7 @@
 import android.content.IntentFilter;
 import android.telecom.TelecomManager;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.util.Comparator;
@@ -33,18 +34,23 @@
 /**
  * Sorts notifications individually into attention-relevant order.
  */
-public class NotificationComparator
-        implements Comparator<NotificationRecord> {
+class NotificationComparator implements Comparator<NotificationRecord> {
 
     private final Context mContext;
     private final NotificationMessagingUtil mMessagingUtil;
     private String mDefaultPhoneApp;
 
+    /**
+     * Lock that must be held during a sort() call that uses this {@link Comparator}, AND to make
+     * any changes to the state of this object that could affect the results of {@link #compare}.
+     */
+    public final Object mStateLock = new Object();
+
     public NotificationComparator(Context context) {
         mContext = context;
         mContext.registerReceiver(mPhoneAppBroadcastReceiver,
                 new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, mStateLock);
     }
 
     @Override
@@ -212,8 +218,13 @@
     private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mDefaultPhoneApp =
-                    intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+            BackgroundThread.getExecutor().execute(() -> {
+                synchronized (mStateLock) {
+                    mDefaultPhoneApp =
+                            intent.getStringExtra(
+                                    TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+                }
+            });
         }
     };
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
old mode 100644
new mode 100755
index 009e097..e782ea9
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -832,12 +832,32 @@
             allowNotificationListener(userId, cn);
         }
 
+        allowDndPackages(userId);
+
+        setDefaultAssistantForUser(userId);
+    }
+
+    @VisibleForTesting
+    void allowDndPackages(int userId) {
         ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
         for (int i = 0; i < defaultDnds.size(); i++) {
             allowDndPackage(userId, defaultDnds.valueAt(i));
         }
+        if (!isDNDMigrationDone(userId)) {
+            setDNDMigrationDone(userId);
+        }
+    }
 
-        setDefaultAssistantForUser(userId);
+    @VisibleForTesting
+    boolean isDNDMigrationDone(int userId) {
+        return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+                Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+    }
+
+    @VisibleForTesting
+    void setDNDMigrationDone(int userId) {
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
     }
 
     protected void migrateDefaultNAS() {
@@ -1024,6 +1044,24 @@
     }
 
     @VisibleForTesting
+    void resetDefaultDndIfNecessary() {
+        boolean removed = false;
+        final List<UserInfo> activeUsers = mUm.getAliveUsers();
+        for (UserInfo userInfo : activeUsers) {
+            int userId = userInfo.getUserHandle().getIdentifier();
+            if (isDNDMigrationDone(userId)) {
+                continue;
+            }
+            removed |= mConditionProviders.removeDefaultFromConfig(userId);
+            mConditionProviders.resetDefaultFromConfig();
+            allowDndPackages(userId);
+        }
+        if (removed) {
+            handleSavePolicyFile();
+        }
+    }
+
+    @VisibleForTesting
     protected void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized (mPolicyFile) {
@@ -1031,6 +1069,13 @@
             try {
                 infile = mPolicyFile.openRead();
                 readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+
+                // We re-load the default dnd packages to allow the newly added and denined.
+                final boolean isWatch = mPackageManagerClient.hasSystemFeature(
+                        PackageManager.FEATURE_WATCH);
+                if (isWatch) {
+                    resetDefaultDndIfNecessary();
+                }
             } catch (FileNotFoundException e) {
                 // No data yet
                 // Load default managed services approvals
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bb1092..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2608,7 +2608,12 @@
                             for (NotificationChannel channel : r.channels.values()) {
                                 if (!channel.isSoundRestored()) {
                                     Uri uri = channel.getSound();
-                                    Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+                                    Uri restoredUri =
+                                            channel.restoreSoundUri(
+                                                    mContext,
+                                                    uri,
+                                                    true,
+                                                    channel.getAudioAttributes().getUsage());
                                     if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
                                             restoredUri)) {
                                         Log.w(TAG,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b4347e1..773d10b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -103,8 +103,11 @@
             notificationList.get(i).setGlobalSortKey(null);
         }
 
-        // rank each record individually
-        Collections.sort(notificationList, mPreliminaryComparator);
+        // Rank each record individually.
+        // Lock comparator state for consistent compare() results.
+        synchronized (mPreliminaryComparator.mStateLock) {
+            notificationList.sort(mPreliminaryComparator);
+        }
 
         synchronized (mProxyByGroupTmp) {
             // record individual ranking result and nominate proxies for each group
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 5b7b0c1..f56a67c 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -55,7 +55,7 @@
 
     public ZenModeFiltering(Context context) {
         mContext = context;
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, null);
     }
 
     public ZenModeFiltering(Context context, NotificationMessagingUtil messagingUtil) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index dd3604e..5b05b48 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,6 +24,7 @@
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
 import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1514,6 +1515,11 @@
             ai.uid = UserHandle.getUid(userId, ps.getAppId());
             ai.primaryCpuAbi = ps.getPrimaryCpuAbiLegacy();
             ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
+            ai.volumeUuid = ps.getVolumeUuid();
+            ai.storageUuid = StorageManager.convert(ai.volumeUuid);
+            if (ps.isDefaultToDeviceProtectedStorage()) {
+                ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
+            }
             ai.setVersionCode(ps.getVersionCode());
             ai.flags = ps.getFlags();
             ai.privateFlags = ps.getPrivateFlags();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ac52f9f..385dfcb8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4882,6 +4882,7 @@
                     UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
         }
 
+        final boolean isMainUser = (flags & UserInfo.FLAG_MAIN) != 0;
         final boolean isProfile = userTypeDetails.isProfile();
         final boolean isGuest = UserManager.isUserTypeGuest(userType);
         final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
@@ -5028,6 +5029,10 @@
                 }
             } else {
                 userTypeDetails.addDefaultRestrictionsTo(restrictions);
+                if (isMainUser) {
+                    restrictions.remove(UserManager.DISALLOW_OUTGOING_CALLS);
+                    restrictions.remove(UserManager.DISALLOW_SMS);
+                }
             }
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.updateRestrictions(userId, restrictions);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index f7967c0..d5231b5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,7 +295,8 @@
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setCrossProfileIntentFilterAccessControl(
-                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index 0bb969f..a2177e8 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
 
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 
 import libcore.io.IoUtils;
 
@@ -82,8 +82,8 @@
         }
 
         AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0, Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
         mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 56d92fb..1a8c1996 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
 
     private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
         final AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
         return assets;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index c6d8848..4095be7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -83,7 +83,8 @@
                         "slices",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -106,7 +107,8 @@
                         "account_manager",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -121,7 +123,8 @@
                         "account_sync_settings",
                         "notifications",
                         "permissions",
-                        "app_locales");
+                        "app_locales",
+                        "companion");
     }
 
     @Test
@@ -140,7 +143,8 @@
                         "app_locales",
                         "account_manager",
                         "usage_stats",
-                        "shortcut_manager");
+                        "shortcut_manager",
+                        "companion");
     }
 
     private class TestableSystemBackupAgent extends SystemBackupAgent {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 0664ab8..d32b6be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,7 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
+import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -490,6 +493,17 @@
         assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
     }
 
+    @Test
+    public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+        UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
+                UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
+
+        assertThat(mUms.hasUserRestriction(DISALLOW_OUTGOING_CALLS, mainUser.id))
+                .isFalse();
+        assertThat(mUms.hasUserRestriction(DISALLOW_SMS, mainUser.id))
+                .isFalse();
+    }
+
     private void resetUserSwitcherEnabled() {
         mUms.putUserInfo(new UserInfo(USER_ID, "Test User", 0));
         mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index ffe088c..45c951f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -19,14 +19,17 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -307,4 +310,54 @@
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
                         HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
     }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcSupported_writesAtom() {
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcNotSupported_writesAtom() {
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcNotSupported_writesAtom_logReasonWake() {
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index b94ed01..d04c518 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -18,10 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
 import android.content.ServiceConnection;
@@ -51,6 +55,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext.ensureTestableResources();
 
         mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
         mProviders.setCallback(mCallback);
@@ -131,4 +136,21 @@
         verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
         verifyNoMoreInteractions(mCallback);
     }
+
+    @Test
+    public void testRemoveDefaultFromConfig() {
+        final int userId = 0;
+        ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
+
+        when(mContext.getResources().getString(
+                com.android.internal.R.string.config_defaultDndDeniedPackages))
+                .thenReturn("package");
+        mProviders.setPackageOrComponentEnabled(oldDefaultComponent.flattenToString(),
+                userId, true, true /*enabled*/, false /*userSet*/);
+        assertEquals("package", mProviders.getApproved(userId, true));
+
+        mProviders.removeDefaultFromConfig(userId);
+
+        assertTrue(mProviders.getApproved(userId, true).isEmpty());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 22b1127..7b16500 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,15 +15,20 @@
  */
 package com.android.server.notification;
 
+import static android.telecom.TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -31,8 +36,10 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -54,12 +61,14 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -335,6 +344,73 @@
         assertTrue(comp.isImportantPeople(mRecordContact));
     }
 
+    @Test
+    public void testChangeDialerPackageWhileSorting() throws InterruptedException {
+        final int halfList = 100;
+        int userId = UserHandle.myUserId();
+        when(mTm.getDefaultDialerPackage()).thenReturn("B");
+
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        NotificationComparator comparator = new NotificationComparator(mMockContext);
+        verify(mMockContext).registerReceiver(broadcastReceiverCaptor.capture(), any());
+        BroadcastReceiver dialerChangedBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+        ArrayList<NotificationRecord> records = new ArrayList<>();
+        for (int i = 0; i < halfList; i++) {
+            Notification notifCallFromPkgA = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("A", "A", 2 * i, "callA", callUid, callUid,
+                            notifCallFromPkgA, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+
+            Notification notifCallFromPkgB = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("B", "B", 2 * i + 1, "callB", callUid, callUid,
+                            notifCallFromPkgB, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+        }
+
+        CountDownLatch allDone = new CountDownLatch(2);
+        new Thread(() -> {
+            // The lock prevents the other thread from changing the dialer package mid-sort, so:
+            // 1) Results should be "all B before all A" (asserted below).
+            // 2) No "IllegalArgumentException: Comparison method violates its general contract!"
+            synchronized (comparator.mStateLock) {
+                records.sort(comparator);
+                allDone.countDown();
+            }
+        }).start();
+
+        new Thread(() -> {
+            String nextDialer = "A";
+            while (allDone.getCount() == 2) {
+                Intent dialerChangedIntent = new Intent();
+                dialerChangedIntent.putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, nextDialer);
+                dialerChangedBroadcastReceiver.onReceive(mMockContext, dialerChangedIntent);
+                nextDialer = nextDialer.equals("A") ? "B" : "A";
+            }
+            allDone.countDown();
+        }).start();
+
+        allDone.await();
+
+        for (int i = 0; i < halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("B");
+        }
+        for (int i = halfList; i < 2 * halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("A");
+        }
+    }
+
     private NotificationChannel getDefaultChannel() {
         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
                 NotificationManager.IMPORTANCE_LOW);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index bdee99b..e1f3c2b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -117,6 +117,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -12431,6 +12432,21 @@
         verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
     }
 
+    @Test
+    public void testResetDefaultDnd() {
+        TestableNotificationManagerService service = spy(mService);
+        UserInfo user = new UserInfo(0, "owner", 0);
+        when(mUm.getAliveUsers()).thenReturn(List.of(user));
+        doReturn(false).when(service).isDNDMigrationDone(anyInt());
+
+        service.resetDefaultDndIfNecessary();
+
+        verify(mConditionProviders, times(1)).removeDefaultFromConfig(user.id);
+        verify(mConditionProviders, times(1)).resetDefaultFromConfig();
+        verify(service, times(1)).allowDndPackages(user.id);
+        verify(service, times(1)).setDNDMigrationDone(user.id);
+    }
+
     private static <T extends Parcelable> T parcelAndUnparcel(T source,
             Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c242554..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -600,7 +600,7 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setDescription("descriptions for all");
-        channel2.setSound(SOUND_URI, mAudioAttributes);
+        channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
         channel2.setLockscreenVisibility(VISIBILITY_SECRET);
@@ -1374,6 +1374,8 @@
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
         doReturn(localUri)
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+        doReturn(canonicalBasedOnLocal)
+                .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1387,7 +1389,7 @@
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
-        assertEquals(localUri, actualChannel.getSound());
+        assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index 9a93746..e3f8e8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -125,6 +125,7 @@
 
     @Test
     public void testSurfaceChangedOnRotation() {
+        CommonUtils.dismissKeyguard();
         final Instrumentation instrumentation = getInstrumentation();
         final Context context = instrumentation.getContext();
         final Intent intent = new Intent().setComponent(
@@ -137,6 +138,8 @@
         instrumentation.runOnMainSync(() -> activity.setContentView(sv));
         sv.getHolder().addCallback(new SurfaceHolder.Callback() {
             int mInitialTransformHint = -1;
+            int mInitialW;
+            int mInitialH;
 
             @Override
             public void surfaceCreated(@NonNull SurfaceHolder holder) {
@@ -148,7 +151,10 @@
                         sv.getViewRootImpl().getSurfaceControl().getTransformHint();
                 if (mInitialTransformHint == -1) {
                     mInitialTransformHint = transformHint;
-                } else if (mInitialTransformHint == transformHint) {
+                    mInitialW = width;
+                    mInitialH = height;
+                } else if (mInitialTransformHint == transformHint
+                        && (width > height) != (mInitialW > mInitialH)) {
                     // For example, the initial hint is from portrait, so the later changes from
                     // landscape should not receive the same hint.
                     unexpectedTransformHint[0] = true;
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 0d88a0d..befc4a0 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -364,8 +364,10 @@
             if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
                 // We don't count code baked into system image
             } else {
-                codePaths = ArrayUtils.appendElement(String.class, codePaths,
+                if (appInfo.getCodePath() != null) {
+                    codePaths = ArrayUtils.appendElement(String.class, codePaths,
                         appInfo.getCodePath());
+                }
             }
 
             final PackageStats stats = new PackageStats(TAG);
@@ -418,8 +420,10 @@
                 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
                     // We don't count code baked into system image
                 } else {
-                    codePaths = ArrayUtils.appendElement(String.class, codePaths,
-                            appInfo.getCodePath());
+                    if (appInfo.getCodePath() != null) {
+                        codePaths = ArrayUtils.appendElement(String.class, codePaths,
+                                appInfo.getCodePath());
+                    }
                 }
             } catch (NameNotFoundException e) {
                 throw new ParcelableException(e);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
index ef49c7f..cb16191 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
@@ -48,15 +48,15 @@
         BitmapsView(Context c) {
             super(c);
 
-            Log.d("OpenGLRenderer", "Loading sunset1, default options");
+            Log.d("HWUI", "Loading sunset1, default options");
             mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
-            Log.d("OpenGLRenderer", "Loading sunset2, default options");
+            Log.d("HWUI", "Loading sunset2, default options");
             mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
-            Log.d("OpenGLRenderer", "Loading sunset3, forcing ARGB-8888");
+            Log.d("HWUI", "Loading sunset3, forcing ARGB-8888");
             BitmapFactory.Options opts = new BitmapFactory.Options();
             opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mBitmap3 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset3, opts);
-            Log.d("OpenGLRenderer", "    has bitmap alpha? " + mBitmap3.hasAlpha());
+            Log.d("HWUI", "    has bitmap alpha? " + mBitmap3.hasAlpha());
 
             mBitmapPaint = new Paint();
         }
@@ -65,7 +65,7 @@
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
             
-            Log.d("OpenGLRenderer", "================= Draw");
+            Log.d("HWUI", "================= Draw");
 
             canvas.translate(120.0f, 50.0f);
             canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
index 1c82e9b..dbfb4ca 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
@@ -77,7 +77,7 @@
                     canvas.drawPath(mPath, mClearPaint);
                 }
                 canvas.restore();
-                canvas.drawText("OpenGLRenderer", 50.0f, 50.0f, mClearPaint);
+                canvas.drawText("HWUI", 50.0f, 50.0f, mClearPaint);
                 mClearPaint.setColor(0xff000000);
                 canvas.drawRect(800.0f, 100.0f, 900.0f, 200.0f, mClearPaint);
                 mClearPaint.setColor(0x0000ff00);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
index 5192bfe..11a2a41 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
@@ -53,30 +53,30 @@
             super.onDraw(canvas);
 
             int count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count=" + count);
+            Log.d("HWUI", "count=" + count);
             count = canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + count);
+            Log.d("HWUI", "count after save=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after save=" + count);
+            Log.d("HWUI", "getSaveCount after save=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.restoreToCount(count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restoreToCount=" + count);
+            Log.d("HWUI", "count after restoreToCount=" + count);
             count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG);
-            Log.d("OpenGLRenderer", "count after saveLayer=" + count);
+            Log.d("HWUI", "count after saveLayer=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count);
+            Log.d("HWUI", "getSaveCount after saveLayer=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
 
             canvas.save();
             canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f);