Merge "Polish ActivityEmbedding enter/exit PiP (1/n)"
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 4bba338..6a323d6 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -123,17 +123,30 @@
 }
 
 droidstubs {
-    name: "framework-doc-system-stubs",
-    defaults: ["framework-doc-stubs-sources-default"],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
+    name: "android-non-updatable-doc-stubs-module-lib",
+    defaults: [
+        "android-non-updatable-doc-stubs-defaults",
+        "module-classpath-stubs-defaults",
     ],
-    api_levels_sdk_type: "system",
-    extensions_info_file: ":sdk-extensions-info",
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) ",
+    generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
+    write_sdk_values: false,
+}
+
+droidstubs {
+    name: "android-non-updatable-doc-stubs-system-server",
+    defaults: [
+        "android-non-updatable-doc-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) " +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ",
+    generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
+    write_sdk_values: false,
 }
 
 droidstubs {
@@ -151,6 +164,20 @@
     extensions_info_file: ":sdk-extensions-info",
 }
 
+droidstubs {
+    name: "framework-doc-system-stubs",
+    defaults: ["framework-doc-stubs-sources-default"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+    api_levels_annotations_enabled: true,
+    api_levels_annotations_dirs: [
+        "sdk-dir",
+        "api-versions-jars-dir",
+    ],
+    api_levels_sdk_type: "system",
+    extensions_info_file: ":sdk-extensions-info",
+}
+
 /////////////////////////////////////////////////////////////////////
 // API docs are created from the generated stub source files
 // using droiddoc
diff --git a/StubLibraries.bp b/StubLibraries.bp
index bc2e6dd..0e08496 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -411,6 +411,36 @@
     ],
 }
 
+java_library {
+    name: "android_module_stubs_current_with_test_libs",
+    static_libs: [
+        "android_module_lib_stubs_current",
+        "android.test.base.stubs",
+        "android.test.mock.stubs",
+        "android.test.runner.stubs",
+    ],
+    defaults: ["android.jar_defaults"],
+    visibility: [
+        "//visibility:override",
+        "//visibility:private",
+    ],
+}
+
+java_library {
+    name: "android_system_server_stubs_current_with_test_libs",
+    static_libs: [
+        "android_system_server_stubs_current",
+        "android.test.base.stubs.system",
+        "android.test.mock.stubs.system",
+        "android.test.runner.stubs.system",
+    ],
+    defaults: ["android.jar_defaults"],
+    visibility: [
+        "//visibility:override",
+        "//visibility:private",
+    ],
+}
+
 droidstubs {
     name: "api_versions_public",
     srcs: [":android_stubs_current_with_test_libs{.jar}"],
@@ -420,6 +450,7 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
+    api_levels_sdk_type: "public",
     extensions_info_file: ":sdk-extensions-info",
 }
 
@@ -436,6 +467,32 @@
     extensions_info_file: ":sdk-extensions-info",
 }
 
+droidstubs {
+    name: "api_versions_module_lib",
+    srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
+    generate_stubs: false,
+    api_levels_annotations_enabled: true,
+    api_levels_annotations_dirs: [
+        "sdk-dir",
+        "api-versions-jars-dir",
+    ],
+    api_levels_sdk_type: "module-lib",
+    extensions_info_file: ":sdk-extensions-info",
+}
+
+droidstubs {
+    name: "api_versions_system_server",
+    srcs: [":android_system_server_stubs_current_with_test_libs{.jar}"],
+    generate_stubs: false,
+    api_levels_annotations_enabled: true,
+    api_levels_annotations_dirs: [
+        "sdk-dir",
+        "api-versions-jars-dir",
+    ],
+    api_levels_sdk_type: "system-server",
+    extensions_info_file: ":sdk-extensions-info",
+}
+
 /////////////////////////////////////////////////////////////////////
 // hwbinder.stubs provides APIs required for building HIDL Java
 // libraries.
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 901796b..bedfa7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -87,6 +87,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManager;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -116,6 +117,7 @@
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LongArrayQueue;
 import android.util.Pair;
@@ -249,6 +251,7 @@
     private ActivityManagerInternal mActivityManagerInternal;
     private final EconomyManagerInternal mEconomyManagerInternal;
     private PackageManagerInternal mPackageManagerInternal;
+    private BatteryStatsInternal mBatteryStatsInternal;
     private RoleManager mRoleManager;
     private volatile PermissionManagerServiceInternal mLocalPermissionManager;
 
@@ -2113,6 +2116,8 @@
                     LocalServices.getService(AppStandbyInternal.class);
             appStandbyInternal.addListener(new AppStandbyTracker());
 
+            mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class);
+
             mRoleManager = getContext().getSystemService(RoleManager.class);
 
             mMetricsHelper.registerPuller(() -> mAlarmStore);
@@ -4783,8 +4788,12 @@
                             }
                             final ArraySet<Pair<String, Integer>> triggerPackages =
                                     new ArraySet<>();
+                            final IntArray wakeupUids = new IntArray();
                             for (int i = 0; i < triggerList.size(); i++) {
                                 final Alarm a = triggerList.get(i);
+                                if (a.wakeup) {
+                                    wakeupUids.add(a.uid);
+                                }
                                 if (mConstants.USE_TARE_POLICY) {
                                     if (!isExemptFromTare(a)) {
                                         triggerPackages.add(Pair.create(
@@ -4796,6 +4805,11 @@
                                             a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
                                 }
                             }
+                            if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) {
+                                mBatteryStatsInternal.noteCpuWakingActivity(
+                                        BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM, nowELAPSED,
+                                        wakeupUids.toArray());
+                            }
                             deliverAlarmsLocked(triggerList, nowELAPSED);
                             mTemporaryQuotaReserve.cleanUpExpiredQuotas(nowELAPSED);
                             if (mConstants.USE_TARE_POLICY) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index a1a541f..b2ca3a0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -337,7 +337,6 @@
                 removeJobFromBillList(jobStatus, billToJobMap.keyAt(i));
             }
         }
-        addJobToBillList(jobStatus, getRunningBill(jobStatus));
 
         final int uid = jobStatus.getSourceUid();
         if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
@@ -347,6 +346,7 @@
             mTopStartedJobs.add(jobStatus);
             // Top jobs won't count towards quota so there's no need to involve the EconomyManager.
         } else {
+            addJobToBillList(jobStatus, getRunningBill(jobStatus));
             mEconomyManagerInternal.noteOngoingEventStarted(userId, pkgName,
                     getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
         }
@@ -357,9 +357,14 @@
     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
-        mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
-                getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
-        mTopStartedJobs.remove(jobStatus);
+        // If this method is called, then jobStatus.madeActive was never updated, so don't use it
+        // to determine if the EconomyManager was notified.
+        if (!mTopStartedJobs.remove(jobStatus)) {
+            // If the job was started while the app was top, then the EconomyManager wasn't notified
+            // of the job start.
+            mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+                    getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+        }
 
         final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus);
         ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
@@ -382,9 +387,13 @@
             boolean forUpdate) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
-        mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
-                getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
-        mTopStartedJobs.remove(jobStatus);
+        if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) {
+            // Only note the job stop if we previously told the EconomyManager that the job started.
+            // If the job was started while the app was top, then the EconomyManager wasn't notified
+            // of the job start.
+            mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+                    getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+        }
         ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
                 mRegisteredBillsAndJobs.get(userId, pkgName);
         if (billToJobMap != null) {
diff --git a/api/Android.bp b/api/Android.bp
index 07fd850..9306671 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -213,6 +213,24 @@
 }
 
 genrule {
+    name: "sdk-annotations-module-lib.zip",
+    defaults: ["sdk-annotations-defaults"],
+    srcs: [
+        ":android-non-updatable-doc-stubs-module-lib{.annotations.zip}",
+        ":all-modules-module-lib-annotations",
+    ],
+}
+
+genrule {
+    name: "sdk-annotations-system-server.zip",
+    defaults: ["sdk-annotations-defaults"],
+    srcs: [
+        ":android-non-updatable-doc-stubs-system-server{.annotations.zip}",
+        ":all-modules-system-server-annotations",
+    ],
+}
+
+genrule {
     name: "combined-removed-dex",
     visibility: [
         "//frameworks/base/boot",
diff --git a/api/api.go b/api/api.go
index f158041..6a6c493 100644
--- a/api/api.go
+++ b/api/api.go
@@ -148,18 +148,35 @@
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
-func createMergedPublicAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
-	props := fgProps{}
-	props.Name = proptools.StringPtr("all-modules-public-annotations")
-	props.Srcs = createSrcs(modules, "{.public.annotations.zip}")
-	ctx.CreateModule(android.FileGroupFactory, &props)
-}
-
-func createMergedSystemAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
-	props := fgProps{}
-	props.Name = proptools.StringPtr("all-modules-system-annotations")
-	props.Srcs = createSrcs(modules, "{.system.annotations.zip}")
-	ctx.CreateModule(android.FileGroupFactory, &props)
+func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
+	for _, i := range []struct{
+		name    string
+		tag     string
+		modules []string
+	}{
+		{
+			name: "all-modules-public-annotations",
+			tag:  "{.public.annotations.zip}",
+			modules: modules,
+		}, {
+			name: "all-modules-system-annotations",
+			tag:  "{.system.annotations.zip}",
+			modules: modules,
+		}, {
+			name: "all-modules-module-lib-annotations",
+			tag:  "{.module-lib.annotations.zip}",
+			modules: modules,
+		}, {
+			name: "all-modules-system-server-annotations",
+			tag:  "{.system-server.annotations.zip}",
+			modules: system_server_modules,
+		},
+	} {
+		props := fgProps{}
+		props.Name = proptools.StringPtr(i.name)
+		props.Srcs = createSrcs(i.modules, i.tag)
+		ctx.CreateModule(android.FileGroupFactory, &props)
+	}
 }
 
 func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
@@ -172,17 +189,43 @@
 	//    difficult to achieve.
 	modules = remove(modules, art)
 
-	props := genruleProps{}
-	props.Name = proptools.StringPtr("api-versions-xml-public-filtered")
-	props.Tools = []string{"api_versions_trimmer"}
-	props.Out = []string{"api-versions-public-filtered.xml"}
-	props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
-	// Note: order matters: first parameter is the full api-versions.xml
-	// after that the stubs files in any order
-	// stubs files are all modules that export API surfaces EXCEPT ART
-	props.Srcs = append([]string{":api_versions_public{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...)
-	props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
-	ctx.CreateModule(genrule.GenRuleFactory, &props)
+	for _, i := range []struct{
+		name string
+		out  string
+		in   string
+	}{
+		{
+			// We shouldn't need public-filtered or system-filtered.
+			// public-filtered is currently used to lint things that
+			// use the module sdk or the system server sdk, but those
+			// should be switched over to module-filtered and
+			// system-server-filtered, and then public-filtered can
+			// be removed.
+			name: "api-versions-xml-public-filtered",
+			out:  "api-versions-public-filtered.xml",
+			in:   ":api_versions_public{.api_versions.xml}",
+		}, {
+			name: "api-versions-xml-module-lib-filtered",
+			out:  "api-versions-module-lib-filtered.xml",
+			in:   ":api_versions_module_lib{.api_versions.xml}",
+		}, {
+			name: "api-versions-xml-system-server-filtered",
+			out:  "api-versions-system-server-filtered.xml",
+			in:   ":api_versions_system_server{.api_versions.xml}",
+		},
+	} {
+		props := genruleProps{}
+		props.Name = proptools.StringPtr(i.name)
+		props.Out = []string{i.out}
+		// Note: order matters: first parameter is the full api-versions.xml
+		// after that the stubs files in any order
+		// stubs files are all modules that export API surfaces EXCEPT ART
+		props.Srcs = append([]string{i.in}, createSrcs(modules, ".stubs{.jar}")...)
+		props.Tools = []string{"api_versions_trimmer"}
+		props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
+		props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
+		ctx.CreateModule(genrule.GenRuleFactory, &props)
+	}
 }
 
 func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
@@ -279,11 +322,12 @@
 
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
+	system_server_classpath := a.properties.System_server_classpath
 	if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
 		bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
 		sort.Strings(bootclasspath)
 	}
-	createMergedTxts(ctx, bootclasspath, a.properties.System_server_classpath)
+	createMergedTxts(ctx, bootclasspath, system_server_classpath)
 
 	createMergedStubsSrcjar(ctx, bootclasspath)
 
@@ -292,8 +336,7 @@
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
-	createMergedPublicAnnotationsFilegroup(ctx, bootclasspath)
-	createMergedSystemAnnotationsFilegroup(ctx, bootclasspath)
+	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createFilteredApiVersions(ctx, bootclasspath)
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 815ccfe..431f06c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8947,6 +8947,7 @@
 
   public final class AssociationInfo implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public android.os.Parcelable getAssociatedDevice();
     method @Nullable public android.net.MacAddress getDeviceMacAddress();
     method @Nullable public String getDeviceProfile();
     method @Nullable public CharSequence getDisplayName();
@@ -11595,6 +11596,7 @@
     field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
     field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
     field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
+    field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
     field public static final String EXTRA_SESSION = "android.content.pm.extra.SESSION";
     field public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
     field public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
@@ -11651,6 +11653,7 @@
     method public void removeChildSessionId(int);
     method public void removeSplit(@NonNull String) throws java.io.IOException;
     method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
+    method public void requestUserPreapproval(@NonNull android.content.pm.PackageInstaller.PreapprovalDetails, @NonNull android.content.IntentSender);
     method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
     method public void setStagingProgress(float);
     method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 87960d7..0b428d9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7132,6 +7132,7 @@
     field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
     field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
     field public static final int TUNER_VERSION_2_0 = 131072; // 0x20000
+    field public static final int TUNER_VERSION_3_0 = 196608; // 0x30000
     field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
   }
 
@@ -7149,6 +7150,7 @@
     method public long read(@NonNull byte[], long, long);
     method public long seek(long);
     method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
+    method public int setPlaybackBufferStatusCheckIntervalHint(long);
     method public int start();
     method public int stop();
     field public static final int PLAYBACK_STATUS_ALMOST_EMPTY = 2; // 0x2
@@ -7164,6 +7166,7 @@
     method public int detachFilter(@NonNull android.media.tv.tuner.filter.Filter);
     method public int flush();
     method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
+    method public int setRecordBufferStatusCheckIntervalHint(long);
     method public int start();
     method public int stop();
     method public long write(long);
@@ -11159,6 +11162,7 @@
 
   public final class ActivityEvent implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.app.assist.ActivityId getActivityId();
     method @NonNull public android.content.ComponentName getComponentName();
     method public int getEventType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7d19ed4..81aa6da 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -23,6 +23,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.ProcessCapability;
 import android.app.ActivityManager.RestrictionLevel;
+import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -343,7 +344,7 @@
      */
     public abstract void updateActivityUsageStats(
             ComponentName activity, @UserIdInt int userId, int event, IBinder appToken,
-            ComponentName taskRoot);
+            ComponentName taskRoot, ActivityId activityId);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
     public abstract void sendForegroundProfileChanged(@UserIdInt int userId);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index f362204..52ef7fb 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -266,6 +266,12 @@
     private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
 
     /**
+     * See {@link #setDisableStartingWindow}.
+     * @hide
+     */
+    private static final String KEY_DISABLE_STARTING_WINDOW = "android.activity.disableStarting";
+
+    /**
      * See {@link #setPendingIntentLaunchFlags(int)}
      * @hide
      */
@@ -477,6 +483,7 @@
     private PictureInPictureParams mLaunchIntoPipParams;
     private boolean mDismissKeyguard;
     private boolean mIgnorePendingIntentCreatorForegroundState;
+    private boolean mDisableStartingWindow;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -1284,6 +1291,7 @@
         mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD);
         mIgnorePendingIntentCreatorForegroundState = opts.getBoolean(
                 KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE);
+        mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
     }
 
     /**
@@ -1700,6 +1708,22 @@
     }
 
     /**
+     * Sets whether recents disable showing starting window when activity launch.
+     * @hide
+     */
+    @RequiresPermission(START_TASKS_FROM_RECENTS)
+    public void setDisableStartingWindow(boolean disable) {
+        mDisableStartingWindow = disable;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean getDisableStartingWindow() {
+        return mDisableStartingWindow;
+    }
+
+    /**
      * Specifies intent flags to be applied for any activity started from a PendingIntent.
      *
      * @hide
@@ -2210,6 +2234,9 @@
             b.putBoolean(KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE,
                     mIgnorePendingIntentCreatorForegroundState);
         }
+        if (mDisableStartingWindow) {
+            b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 033cffe..e2082f7 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -650,7 +650,7 @@
             if (decorView != null) {
                 Drawable drawable = decorView.getBackground();
                 if (drawable != null) {
-                    drawable.setAlpha(1);
+                    drawable.setAlpha(255);
                 }
             }
         }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ddfbc68..302d146 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -159,6 +159,7 @@
     void clearRequestedListenerHints(in INotificationListener token);
     void requestHintsFromListener(in INotificationListener token, int hints);
     int getHintsFromListener(in INotificationListener token);
+    int getHintsFromListenerNoToken();
     void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
     int getInterruptionFilterFromListener(in INotificationListener token);
     void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 392f52a..f6d27ad 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -66,9 +66,9 @@
 
 /**
  * Class to notify the user of events that happen.  This is how you tell
- * the user that something has happened in the background. {@more}
+ * the user that something has happened in the background.
  *
- * Notifications can take different forms:
+ * <p>Notifications can take different forms:
  * <ul>
  *      <li>A persistent icon that goes in the status bar and is accessible
  *          through the launcher, (when the user selects it, a designated Intent
diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java
new file mode 100644
index 0000000..3758cdb
--- /dev/null
+++ b/core/java/android/companion/AssociatedDevice.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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 android.companion;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Loose wrapper around device parcelable. Device can be one of three types:
+ *
+ * <ul>
+ *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
+ *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
+ *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+ * </ul>
+ *
+ * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from
+ * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app,
+ * and should only be used internally.
+ *
+ * @hide
+ */
+public final class AssociatedDevice implements Parcelable {
+    private static final int CLASSIC_BLUETOOTH = 0;
+    private static final int BLUETOOTH_LE = 1;
+    private static final int WIFI = 2;
+
+    @NonNull private final Parcelable mDevice;
+
+    public AssociatedDevice(@NonNull Parcelable device) {
+        mDevice = device;
+    }
+
+    private AssociatedDevice(Parcel in) {
+        Creator<? extends Parcelable> creator = getDeviceCreator(in.readInt());
+        mDevice = creator.createFromParcel(in);
+    }
+
+    /**
+     * Return device info. Cast to expected device type.
+     */
+    @NonNull
+    public Parcelable getDevice() {
+        return mDevice;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // Parcel device type with int for efficiency
+        dest.writeInt(getDeviceType());
+        mDevice.writeToParcel(dest, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private int getDeviceType() {
+        if (mDevice instanceof android.bluetooth.BluetoothDevice) return CLASSIC_BLUETOOTH;
+        if (mDevice instanceof android.bluetooth.le.ScanResult) return BLUETOOTH_LE;
+        if (mDevice instanceof android.net.wifi.ScanResult) return WIFI;
+        throw new UnsupportedOperationException("Unsupported device type.");
+    }
+
+    private static Creator<? extends Parcelable> getDeviceCreator(int deviceType) {
+        switch (deviceType) {
+            case CLASSIC_BLUETOOTH: return android.bluetooth.BluetoothDevice.CREATOR;
+            case BLUETOOTH_LE: return android.bluetooth.le.ScanResult.CREATOR;
+            case WIFI: return android.net.wifi.ScanResult.CREATOR;
+            default: throw new UnsupportedOperationException("Unsupported device type.");
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<AssociatedDevice> CREATOR =
+            new Parcelable.Creator<AssociatedDevice>() {
+                @Override
+                public AssociatedDevice[] newArray(int size) {
+                    return new AssociatedDevice[size];
+                }
+
+                @Override
+                public AssociatedDevice createFromParcel(@NonNull Parcel in) {
+                    return new AssociatedDevice(in);
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "AssociatedDevice { "
+                + "device = " + mDevice
+                + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AssociatedDevice that = (AssociatedDevice) o;
+        if (getDeviceType() != that.getDeviceType()) return false;
+
+        // TODO(b/31972115): Take out this whole part ¯\_(ツ)_/¯
+        if (mDevice instanceof android.bluetooth.le.ScanResult
+                || mDevice instanceof android.net.wifi.ScanResult) {
+            return mDevice.toString().equals(that.mDevice.toString());
+        }
+
+        return java.util.Objects.equals(mDevice, that.mDevice);
+    }
+
+    @Override
+    public int hashCode() {
+        return java.util.Objects.hash(mDevice);
+    }
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 93748f8..93964b3 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -52,6 +52,7 @@
     private final @Nullable MacAddress mDeviceMacAddress;
     private final @Nullable CharSequence mDisplayName;
     private final @Nullable String mDeviceProfile;
+    private final @Nullable AssociatedDevice mAssociatedDevice;
 
     private final boolean mSelfManaged;
     private final boolean mNotifyOnDeviceNearby;
@@ -78,8 +79,9 @@
      */
     public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
-            @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
-            boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
+            @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+            boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked,
+            long timeApprovedMs, long lastTimeConnectedMs) {
         if (id <= 0) {
             throw new IllegalArgumentException("Association ID should be greater than 0");
         }
@@ -96,6 +98,7 @@
         mDeviceMacAddress = macAddress;
         mDisplayName = displayName;
         mDeviceProfile = deviceProfile;
+        mAssociatedDevice = associatedDevice;
 
         mSelfManaged = selfManaged;
         mNotifyOnDeviceNearby = notifyOnDeviceNearby;
@@ -160,6 +163,24 @@
     }
 
     /**
+     * Companion device that was associated. Note that this field is not persisted across sessions.
+     *
+     * Cast to expected device type before use:
+     *
+     * <ul>
+     *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
+     *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
+     *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+     * </ul>
+     *
+     * @return the companion device that was associated, or {@code null} if the device is
+     *         self-managed.
+     */
+    public @Nullable Parcelable getAssociatedDevice() {
+        return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice();
+    }
+
+    /**
      * @return whether the association is managed by the companion application it belongs to.
      * @see AssociationRequest.Builder#setSelfManaged(boolean)
      * @hide
@@ -260,6 +281,7 @@
                 + ", mDisplayName='" + mDisplayName + '\''
                 + ", mDeviceProfile='" + mDeviceProfile + '\''
                 + ", mSelfManaged=" + mSelfManaged
+                + ", mAssociatedDevice=" + mAssociatedDevice
                 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
                 + ", mRevoked=" + mRevoked
                 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
@@ -284,14 +306,15 @@
                 && Objects.equals(mPackageName, that.mPackageName)
                 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                 && Objects.equals(mDisplayName, that.mDisplayName)
-                && Objects.equals(mDeviceProfile, that.mDeviceProfile);
+                && Objects.equals(mDeviceProfile, that.mDeviceProfile)
+                && Objects.equals(mAssociatedDevice, that.mAssociatedDevice);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
-                mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
-                mLastTimeConnectedMs);
+                mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
+                mTimeApprovedMs, mLastTimeConnectedMs);
     }
 
     @Override
@@ -309,6 +332,7 @@
         dest.writeTypedObject(mDeviceMacAddress, 0);
         dest.writeCharSequence(mDisplayName);
         dest.writeString(mDeviceProfile);
+        dest.writeTypedObject(mAssociatedDevice, 0);
 
         dest.writeBoolean(mSelfManaged);
         dest.writeBoolean(mNotifyOnDeviceNearby);
@@ -326,6 +350,7 @@
         mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
         mDisplayName = in.readCharSequence();
         mDeviceProfile = in.readString();
+        mAssociatedDevice = in.readTypedObject(AssociatedDevice.CREATOR);
 
         mSelfManaged = in.readBoolean();
         mNotifyOnDeviceNearby = in.readBoolean();
@@ -433,6 +458,7 @@
                     mOriginalInfo.mDeviceMacAddress,
                     mOriginalInfo.mDisplayName,
                     mOriginalInfo.mDeviceProfile,
+                    mOriginalInfo.mAssociatedDevice,
                     mOriginalInfo.mSelfManaged,
                     mNotifyOnDeviceNearby,
                     mRevoked,
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 75ab115..a2277e9 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -154,6 +154,11 @@
     private @Nullable CharSequence mDisplayName;
 
     /**
+     * The device that was associated. Will be null for "self-managed" association.
+     */
+    private @Nullable AssociatedDevice mAssociatedDevice;
+
+    /**
      * Whether the association is to be managed by the companion application.
      */
     private final boolean mSelfManaged;
@@ -307,6 +312,11 @@
     }
 
     /** @hide */
+    public void setAssociatedDevice(AssociatedDevice associatedDevice) {
+        mAssociatedDevice = associatedDevice;
+    }
+
+    /** @hide */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public List<DeviceFilter<?>> getDeviceFilters() {
@@ -439,6 +449,16 @@
 
 
     /**
+     * The device that was associated. Will be null for "self-managed" association.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @Nullable AssociatedDevice getAssociatedDevice() {
+        return mAssociatedDevice;
+    }
+
+    /**
      * The app package name of the application the association will belong to.
      * Populated by the system.
      *
@@ -503,6 +523,7 @@
                 "deviceFilters = " + mDeviceFilters + ", " +
                 "deviceProfile = " + mDeviceProfile + ", " +
                 "displayName = " + mDisplayName + ", " +
+                "associatedDevice = " + mAssociatedDevice + ", " +
                 "selfManaged = " + mSelfManaged + ", " +
                 "forceConfirmation = " + mForceConfirmation + ", " +
                 "packageName = " + mPackageName + ", " +
@@ -530,6 +551,7 @@
                 && Objects.equals(mDeviceFilters, that.mDeviceFilters)
                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                 && Objects.equals(mDisplayName, that.mDisplayName)
+                && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
                 && mSelfManaged == that.mSelfManaged
                 && mForceConfirmation == that.mForceConfirmation
                 && Objects.equals(mPackageName, that.mPackageName)
@@ -550,6 +572,7 @@
         _hash = 31 * _hash + Objects.hashCode(mDeviceFilters);
         _hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
         _hash = 31 * _hash + Objects.hashCode(mDisplayName);
+        _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
         _hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
         _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
         _hash = 31 * _hash + Objects.hashCode(mPackageName);
@@ -568,17 +591,19 @@
 
         int flg = 0;
         if (mSingleDevice) flg |= 0x1;
-        if (mSelfManaged) flg |= 0x10;
-        if (mForceConfirmation) flg |= 0x20;
-        if (mSkipPrompt) flg |= 0x400;
+        if (mSelfManaged) flg |= 0x20;
+        if (mForceConfirmation) flg |= 0x40;
+        if (mSkipPrompt) flg |= 0x800;
         if (mDeviceProfile != null) flg |= 0x4;
         if (mDisplayName != null) flg |= 0x8;
-        if (mPackageName != null) flg |= 0x40;
-        if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+        if (mAssociatedDevice != null) flg |= 0x10;
+        if (mPackageName != null) flg |= 0x80;
+        if (mDeviceProfilePrivilegesDescription != null) flg |= 0x200;
         dest.writeInt(flg);
         dest.writeParcelableList(mDeviceFilters, flags);
         if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
         if (mDisplayName != null) dest.writeCharSequence(mDisplayName);
+        if (mAssociatedDevice != null) dest.writeTypedObject(mAssociatedDevice, flags);
         if (mPackageName != null) dest.writeString(mPackageName);
         dest.writeInt(mUserId);
         if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
@@ -598,18 +623,20 @@
 
         int flg = in.readInt();
         boolean singleDevice = (flg & 0x1) != 0;
-        boolean selfManaged = (flg & 0x10) != 0;
-        boolean forceConfirmation = (flg & 0x20) != 0;
-        boolean skipPrompt = (flg & 0x400) != 0;
+        boolean selfManaged = (flg & 0x20) != 0;
+        boolean forceConfirmation = (flg & 0x40) != 0;
+        boolean skipPrompt = (flg & 0x800) != 0;
         List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
         in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
                 (Class<android.companion.DeviceFilter<?>>) (Class<?>)
                         android.companion.DeviceFilter.class);
         String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
         CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
-        String packageName = (flg & 0x40) == 0 ? null : in.readString();
+        AssociatedDevice associatedDevice = (flg & 0x10) == 0
+                ? null : (AssociatedDevice) in.readTypedObject(AssociatedDevice.CREATOR);
+        String packageName = (flg & 0x80) == 0 ? null : in.readString();
         int userId = in.readInt();
-        String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString();
+        String deviceProfilePrivilegesDescription = (flg & 0x200) == 0 ? null : in.readString();
         long creationTime = in.readLong();
 
         this.mSingleDevice = singleDevice;
@@ -620,6 +647,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 DeviceProfile.class, null, mDeviceProfile);
         this.mDisplayName = displayName;
+        this.mAssociatedDevice = associatedDevice;
         this.mSelfManaged = selfManaged;
         this.mForceConfirmation = forceConfirmation;
         this.mPackageName = packageName;
@@ -648,10 +676,10 @@
     };
 
     @DataClass.Generated(
-            time = 1649179640045L,
+            time = 1663088980513L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
-            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final  boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic  boolean isSelfManaged()\npublic  boolean isForceConfirmation()\npublic  boolean isSingleDevice()\npublic  void setPackageName(java.lang.String)\npublic  void setUserId(int)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic  void setDisplayName(java.lang.CharSequence)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
+            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final  boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate @android.annotation.Nullable android.companion.AssociatedDevice mAssociatedDevice\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic  boolean isSelfManaged()\npublic  boolean isForceConfirmation()\npublic  boolean isSingleDevice()\npublic  void setPackageName(java.lang.String)\npublic  void setUserId(int)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic  void setDisplayName(java.lang.CharSequence)\npublic  void setAssociatedDevice(android.companion.AssociatedDevice)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4142bce..90973c3 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -145,7 +145,7 @@
      *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
      * </ul>
      *
-     * @deprecated use {@link #EXTRA_ASSOCIATION} instead.
+     * @deprecated use {@link AssociationInfo#getAssociatedDevice()} instead.
      */
     @Deprecated
     public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 8d6c8e8d..1fc6bda 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -20,6 +20,7 @@
 import android.content.pm.DataLoaderParamsParcel;
 import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.PackageInstaller;
 import android.content.IntentSender;
 import android.os.ParcelFileDescriptor;
 
@@ -58,4 +59,6 @@
 
     boolean isStaged();
     int getInstallFlags();
+
+    void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5b18273..d2fb1fb 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -170,6 +170,10 @@
     /** {@hide} */
     public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
 
+    /** @hide */
+    public static final String ACTION_CONFIRM_PRE_APPROVAL =
+            "android.content.pm.action.CONFIRM_PRE_APPROVAL";
+
     /**
      * An integer session ID that an operation is working with.
      *
@@ -207,6 +211,17 @@
     public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
 
     /**
+     * Indicate if the status is for a pre-approval request.
+     *
+     * If callers use the same {@link IntentSender} for both
+     * {@link Session#requestUserPreapproval(PreapprovalDetails, IntentSender)} and
+     * {@link Session#commit(IntentSender)}, they can use this to differentiate between them.
+     *
+     * @see Intent#getBooleanExtra(String, boolean)
+     */
+    public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
+
+    /**
      * Detailed string representation of the status, including raw details that
      * are useful for debugging.
      *
@@ -1667,6 +1682,41 @@
                 e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * Attempt to request the approval before committing this session.
+         *
+         * For installers that have been granted the
+         * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
+         * permission, they can request the approval from users before
+         * {@link Session#commit(IntentSender)} is called. This may require user intervention as
+         * well. The result of the request will be reported through the given callback.
+         *
+         * @param details the adequate context to this session for requesting the approval from
+         *                users prior to commit.
+         * @param statusReceiver called when the state of the session changes.
+         *                       Intents sent to this receiver contain
+         *                       {@link #EXTRA_STATUS}. Refer to the individual
+         *                       status codes on how to handle them.
+         *
+         * @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
+         * @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
+         * @throws IllegalStateException if called on a multi-package session (no matter
+         *                               the parent session or any of the children sessions).
+         * @throws IllegalStateException if called again after this method has been called on
+         *                               this session.
+         * @throws SecurityException when the caller does not own this session.
+         */
+        public void requestUserPreapproval(@NonNull PreapprovalDetails details,
+                @NonNull IntentSender statusReceiver) {
+            Preconditions.checkArgument(details != null, "preapprovalDetails cannot be null.");
+            Preconditions.checkArgument(statusReceiver != null, "statusReceiver cannot be null.");
+            try {
+                mSession.requestUserPreapproval(details, statusReceiver);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -2631,6 +2681,9 @@
         /** {@hide} */
         public int installerUid;
 
+        /** @hide */
+        public boolean isPreapprovalRequested;
+
         /** {@hide} */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public SessionInfo() {
@@ -2678,6 +2731,7 @@
             mSessionErrorCode = source.readInt();
             mSessionErrorMessage = source.readString();
             isCommitted = source.readBoolean();
+            isPreapprovalRequested = source.readBoolean();
             rollbackDataPolicy = source.readInt();
             createdMillis = source.readLong();
             requireUserAction = source.readInt();
@@ -3099,7 +3153,7 @@
         }
 
         /**
-         * Returns the set of session IDs that will be committed when this session is commited if
+         * Returns the set of session IDs that will be committed when this session is committed if
          * this session is a multi-package session.
          */
         @NonNull
@@ -3257,6 +3311,7 @@
             dest.writeInt(mSessionErrorCode);
             dest.writeString(mSessionErrorMessage);
             dest.writeBoolean(isCommitted);
+            dest.writeBoolean(isPreapprovalRequested);
             dest.writeInt(rollbackDataPolicy);
             dest.writeLong(createdMillis);
             dest.writeInt(requireUserAction);
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
new file mode 100644
index 0000000..122c54a
--- /dev/null
+++ b/core/java/android/credentials/ui/Entry.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2022 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 android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.app.slice.Slice;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A credential, save, or action entry to be rendered.
+ *
+ * @hide
+ */
+public class Entry implements Parcelable {
+    // TODO: move to jetpack.
+    public static final String VERSION = "v1";
+    public static final Uri CREDENTIAL_MANAGER_ENTRY_URI = Uri.parse("credentialmanager.slice");
+    public static final String HINT_TITLE = "hint_title";
+    public static final String HINT_SUBTITLE = "hint_subtitle";
+    public static final String HINT_ICON = "hint_icon";
+
+    /**
+    * The intent extra key for the action chip {@code Entry} list when launching the UX activities.
+    */
+    public static final String EXTRA_ENTRY_LIST_ACTION_CHIP =
+            "android.credentials.ui.extra.ENTRY_LIST_ACTION_CHIP";
+    /**
+    * The intent extra key for the credential / save {@code Entry} list when launching the UX
+    * activities.
+    */
+    public static final String EXTRA_ENTRY_LIST_CREDENTIAL =
+            "android.credentials.ui.extra.ENTRY_LIST_CREDENTIAL";
+    /**
+    * The intent extra key for the authentication action {@code Entry} when launching the UX
+    * activities.
+    */
+    public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION =
+            "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
+
+    // TODO: may be changed to other type depending on the service implementation.
+    private final int mId;
+
+    @NonNull
+    private final Slice mSlice;
+
+    protected Entry(@NonNull Parcel in) {
+        int entryId = in.readInt();
+        Slice slice = Slice.CREATOR.createFromParcel(in);
+
+        mId = entryId;
+        mSlice = slice;
+        AnnotationValidations.validate(NonNull.class, null, mSlice);
+    }
+
+    public Entry(int id, @NonNull Slice slice) {
+        mId = id;
+        mSlice = slice;
+    }
+
+    /**
+    * Returns the id of this entry that's unique within the context of the CredentialManager
+    * request.
+    */
+    public int getEntryId() {
+        return mId;
+    }
+
+    /**
+    * Returns the Slice to be rendered.
+    */
+    @NonNull
+    public Slice getSlice() {
+        return mSlice;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        mSlice.writeToParcel(dest, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
+        @Override
+        public Entry createFromParcel(@NonNull Parcel in) {
+            return new Entry(in);
+        }
+
+        @Override
+        public Entry[] newArray(int size) {
+            return new Entry[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 49e5e49..18e6ba4 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -17,11 +17,15 @@
 package android.credentials.ui;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.AnnotationValidations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Holds metadata and credential entries for a single provider.
  *
@@ -36,13 +40,24 @@
     public static final String EXTRA_PROVIDER_DATA_LIST =
             "android.credentials.ui.extra.PROVIDER_DATA_LIST";
 
-    // TODO: add entry data.
-
     @NonNull
     private final String mPackageName;
+    @NonNull
+    private final List<Entry> mCredentialEntries;
+    @NonNull
+    private final List<Entry> mActionChips;
+    @Nullable
+    private final Entry mAuthenticationEntry;
 
-    public ProviderData(@NonNull String packageName) {
+    public ProviderData(
+            @NonNull String packageName,
+            @NonNull List<Entry> credentialEntries,
+            @NonNull List<Entry> actionChips,
+            @Nullable Entry authenticationEntry) {
         mPackageName = packageName;
+        mCredentialEntries = credentialEntries;
+        mActionChips = actionChips;
+        mAuthenticationEntry = authenticationEntry;
     }
 
     /** Returns the provider package name. */
@@ -51,15 +66,46 @@
         return mPackageName;
     }
 
+    @NonNull
+    public List<Entry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    @NonNull
+    public List<Entry> getActionChips() {
+        return mActionChips;
+    }
+
+    @Nullable
+    public Entry getAuthenticationEntry() {
+        return mAuthenticationEntry;
+    }
+
     protected ProviderData(@NonNull Parcel in) {
         String packageName = in.readString8();
         mPackageName = packageName;
         AnnotationValidations.validate(NonNull.class, null, mPackageName);
+
+        List<Entry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, Entry.CREATOR);
+        mCredentialEntries = credentialEntries;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
+
+        List<Entry> actionChips  = new ArrayList<>();
+        in.readTypedList(actionChips, Entry.CREATOR);
+        mActionChips = actionChips;
+        AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+        Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
+        mAuthenticationEntry = authenticationEntry;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mPackageName);
+        dest.writeTypedList(mCredentialEntries);
+        dest.writeTypedList(mActionChips);
+        dest.writeTypedObject(mAuthenticationEntry, flags);
     }
 
     @Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 8305843..bc63686 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -299,7 +299,7 @@
             if (isLegacyCompatibilityWalEnabled()) {
                 return SQLiteCompatibilityWalFlags.getWALSyncMode();
             } else {
-                return SQLiteGlobal.getDefaultSyncMode();
+                return SQLiteGlobal.getWALSyncMode();
             }
         } else {
             return SQLiteGlobal.getDefaultSyncMode();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index adeb722..7a55a5c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2017,7 +2017,7 @@
         public static final int EVENT_PACKAGE_INSTALLED = 0x000b;
         // Event for a package being uninstalled.
         public static final int EVENT_PACKAGE_UNINSTALLED = 0x000c;
-        // Event for a package being uninstalled.
+        // Event for an alarm being sent out to an app.
         public static final int EVENT_ALARM = 0x000d;
         // Record that we have decided we need to collect new stats data.
         public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000e;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 2c0be87..3bf9ca0 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -115,6 +115,7 @@
     private final int mMaxStreamVolume;
     private boolean mAffectedByRingerMode;
     private boolean mNotificationOrRing;
+    private final boolean mNotifAliasRing;
     private final Receiver mReceiver = new Receiver();
 
     private Handler mHandler;
@@ -179,6 +180,8 @@
         if (mNotificationOrRing) {
             mRingerMode = mAudioManager.getRingerModeInternal();
         }
+        mNotifAliasRing = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_alias_ring_notif_stream_types);
         mZenMode = mNotificationManager.getZenMode();
 
         if (hasAudioProductStrategies()) {
@@ -280,7 +283,15 @@
         if (zenMuted) {
             mSeekBar.setProgress(mLastAudibleStreamVolume, true);
         } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
-            mSeekBar.setProgress(0, true);
+            /**
+             * the first variable above is preserved and the conditions below are made explicit
+             * so that when user attempts to slide the notification seekbar out of vibrate the
+             * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
+             */
+            if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+                    || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
+                mSeekBar.setProgress(0, true);
+            }
         } else if (mMuted) {
             mSeekBar.setProgress(0, true);
         } else {
@@ -354,6 +365,7 @@
         // set the time of stop volume
         if ((mStreamType == AudioManager.STREAM_VOICE_CALL
                 || mStreamType == AudioManager.STREAM_RING
+                || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
                 || mStreamType == AudioManager.STREAM_ALARM)) {
             sStopVolumeTime = java.lang.System.currentTimeMillis();
         }
@@ -632,8 +644,8 @@
         }
 
         private void updateVolumeSlider(int streamType, int streamValue) {
-            final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
-                    : (streamType == mStreamType);
+            final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
+                    ? isNotificationOrRing(streamType) : streamType == mStreamType;
             if (mSeekBar != null && streamMatch && streamValue != -1) {
                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
                         || streamValue == 0;
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index d286942..699f42b 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.app.assist.ActivityId;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.os.Parcel;
@@ -80,14 +81,25 @@
 
     private final @NonNull ComponentName mComponentName;
     private final @ActivityEventType int mType;
+    private final @NonNull ActivityId mActivityId;
 
     /** @hide */
-    public ActivityEvent(@NonNull ComponentName componentName, @ActivityEventType int type) {
+    public ActivityEvent(@NonNull ActivityId activityId,
+            @NonNull ComponentName componentName, @ActivityEventType int type) {
+        mActivityId = activityId;
         mComponentName = componentName;
         mType = type;
     }
 
     /**
+     * Gets the ActivityId of the activity associated with the event.
+     */
+    @NonNull
+    public ActivityId getActivityId() {
+        return mActivityId;
+    }
+
+    /**
      * Gests the {@link ComponentName} of the activity associated with the event.
      */
     @NonNull
@@ -129,6 +141,7 @@
     @Override
     public String toString() {
         return new StringBuilder("ActivityEvent[").append(mComponentName.toShortString())
+                .append(", ActivityId: ").append(mActivityId)
                 .append("]:").append(getTypeAsString(mType)).toString();
     }
 
@@ -141,6 +154,7 @@
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeParcelable(mComponentName, flags);
         parcel.writeInt(mType);
+        parcel.writeParcelable(mActivityId, flags);
     }
 
     public static final @android.annotation.NonNull Creator<ActivityEvent> CREATOR =
@@ -149,9 +163,12 @@
         @Override
         @NonNull
         public ActivityEvent createFromParcel(@NonNull Parcel parcel) {
-            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
+            final ComponentName componentName =
+                    parcel.readParcelable(null, ComponentName.class);
             final int eventType = parcel.readInt();
-            return new ActivityEvent(componentName, eventType);
+            final ActivityId activityId =
+                    parcel.readParcelable(null, ActivityId.class);
+            return new ActivityEvent(activityId, componentName, eventType);
         }
 
         @Override
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 4324442..aa45c20 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -42,8 +42,11 @@
     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
         @Override
         public void startDream(WindowManager.LayoutParams layoutParams,
-                IDreamOverlayCallback callback) {
+                IDreamOverlayCallback callback, String dreamComponent,
+                boolean shouldShowComplications) {
             mDreamOverlayCallback = callback;
+            mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
+            mShowComplications = shouldShowComplications;
             onStartDream(layoutParams);
         }
     };
@@ -56,10 +59,6 @@
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
-        mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
-                DreamService.DEFAULT_SHOW_COMPLICATIONS);
-        mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT,
-                ComponentName.class);
         return mDreamOverlay.asBinder();
     }
 
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 1391326..3c1fef0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -31,9 +31,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -68,6 +68,8 @@
 
 import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -75,7 +77,8 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -211,20 +214,8 @@
     private static final String DREAM_META_DATA_ROOT_TAG = "dream";
 
     /**
-     * Extra containing a boolean for whether to show complications on the overlay.
-     * @hide
-     */
-    public static final String EXTRA_SHOW_COMPLICATIONS =
-            "android.service.dreams.SHOW_COMPLICATIONS";
-
-    /**
-     * Extra containing the component name for the active dream.
-     * @hide
-     */
-    public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT";
-
-    /**
      * The default value for whether to show complications on the overlay.
+     *
      * @hide
      */
     public static final boolean DEFAULT_SHOW_COMPLICATIONS = false;
@@ -248,80 +239,72 @@
 
     private boolean mDebug = false;
 
+    private ComponentName mDreamComponent;
+    private boolean mShouldShowComplications;
+
     private DreamServiceWrapper mDreamServiceWrapper;
     private Runnable mDispatchAfterOnAttachedToWindow;
 
-    private final OverlayConnection mOverlayConnection;
+    private OverlayConnection mOverlayConnection;
 
-    private static class OverlayConnection implements ServiceConnection {
+    private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
         // Overlay set during onBind.
         private IDreamOverlay mOverlay;
-        // A Queue of pending requests to execute on the overlay.
-        private final ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+        // A list of pending requests to execute on the overlay.
+        private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
 
-        private boolean mBound;
-
-        OverlayConnection() {
-            mRequests = new ArrayDeque<>();
-        }
-
-        public void bind(Context context, @Nullable ComponentName overlayService,
-                ComponentName dreamService) {
-            if (overlayService == null) {
-                return;
+        private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
+            @Override
+            public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
+                    IDreamOverlay service) {
+                mOverlay = service;
+                for (Consumer<IDreamOverlay> consumer : mConsumers) {
+                    consumer.accept(mOverlay);
+                }
             }
 
-            final ServiceInfo serviceInfo = fetchServiceInfo(context, dreamService);
-
-            final Intent overlayIntent = new Intent();
-            overlayIntent.setComponent(overlayService);
-            overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
-                    fetchShouldShowComplications(context, serviceInfo));
-            overlayIntent.putExtra(EXTRA_DREAM_COMPONENT, dreamService);
-
-            context.bindService(overlayIntent,
-                    this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
-            mBound = true;
-        }
-
-        public void unbind(Context context) {
-            if (!mBound) {
-                return;
+            @Override
+            public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
+                    int reason) {
+                mOverlay = null;
             }
+        };
 
-            context.unbindService(this);
-            mBound = false;
-        }
-
-        public void request(Consumer<IDreamOverlay> request) {
-            mRequests.push(request);
-            evaluate();
-        }
-
-        private void evaluate() {
-            if (mOverlay == null) {
-                return;
-            }
-
-            // Any new requests that arrive during this loop will be processed synchronously after
-            // the loop exits.
-            while (!mRequests.isEmpty()) {
-                final Consumer<IDreamOverlay> request = mRequests.pop();
-                request.accept(mOverlay);
-            }
+        OverlayConnection(Context context,
+                Executor executor,
+                Handler handler,
+                ServiceTransformer<IDreamOverlay> transformer,
+                Intent serviceIntent,
+                int flags,
+                int minConnectionDurationMs,
+                int maxReconnectAttempts,
+                int baseReconnectDelayMs) {
+            super(context, executor, handler, transformer, serviceIntent, flags,
+                    minConnectionDurationMs,
+                    maxReconnectAttempts, baseReconnectDelayMs);
         }
 
         @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            // Store Overlay and execute pending requests.
-            mOverlay = IDreamOverlay.Stub.asInterface(service);
-            evaluate();
+        public boolean bind() {
+            addCallback(mCallback);
+            return super.bind();
         }
 
         @Override
-        public void onServiceDisconnected(ComponentName name) {
-            // Clear Overlay binder to prevent further request processing.
-            mOverlay = null;
+        public void unbind() {
+            removeCallback(mCallback);
+            super.unbind();
+        }
+
+        public void addConsumer(Consumer<IDreamOverlay> consumer) {
+            mConsumers.add(consumer);
+            if (mOverlay != null) {
+                consumer.accept(mOverlay);
+            }
+        }
+
+        public void removeConsumer(Consumer<IDreamOverlay> consumer) {
+            mConsumers.remove(consumer);
         }
     }
 
@@ -336,7 +319,6 @@
 
     public DreamService() {
         mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
-        mOverlayConnection = new OverlayConnection();
     }
 
     /**
@@ -532,7 +514,7 @@
         return mWindow;
     }
 
-   /**
+    /**
      * Inflates a layout resource and set it to be the content view for this Dream.
      * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
      *
@@ -955,6 +937,11 @@
     @Override
     public void onCreate() {
         if (mDebug) Slog.v(mTag, "onCreate()");
+
+        mDreamComponent = new ComponentName(this, getClass());
+        mShouldShowComplications = fetchShouldShowComplications(this /*context*/,
+                fetchServiceInfo(this /*context*/, mDreamComponent));
+
         super.onCreate();
     }
 
@@ -996,13 +983,26 @@
     public final IBinder onBind(Intent intent) {
         if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
         mDreamServiceWrapper = new DreamServiceWrapper();
+        final ComponentName overlayComponent = intent.getParcelableExtra(
+                EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
 
         // Connect to the overlay service if present.
-        if (!mWindowless) {
-            mOverlayConnection.bind(
+        if (!mWindowless && overlayComponent != null) {
+            final Resources resources = getResources();
+            final Intent overlayIntent = new Intent().setComponent(overlayComponent);
+
+            mOverlayConnection = new OverlayConnection(
                     /* context= */ this,
-                    intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT, android.content.ComponentName.class),
-                    new ComponentName(this, getClass()));
+                    getMainExecutor(),
+                    mHandler,
+                    IDreamOverlay.Stub::asInterface,
+                    overlayIntent,
+                    /* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                    resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
+                    resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
+                    resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
+
+            mOverlayConnection.bind();
         }
 
         return mDreamServiceWrapper;
@@ -1011,7 +1011,9 @@
     @Override
     public boolean onUnbind(Intent intent) {
         // We must unbind from any overlay connection if we are unbound before finishing.
-        mOverlayConnection.unbind(this);
+        if (mOverlayConnection != null) {
+            mOverlayConnection.unbind();
+        }
 
         return super.onUnbind(intent);
     }
@@ -1040,7 +1042,9 @@
         }
         mFinished = true;
 
-        mOverlayConnection.unbind(this);
+        if (mOverlayConnection != null) {
+            mOverlayConnection.unbind();
+        }
 
         if (mDreamToken == null) {
             Slog.w(mTag, "Finish was called before the dream was attached.");
@@ -1337,19 +1341,26 @@
 
         mWindow.getDecorView().addOnAttachStateChangeListener(
                 new View.OnAttachStateChangeListener() {
+                    private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+
                     @Override
                     public void onViewAttachedToWindow(View v) {
                         mDispatchAfterOnAttachedToWindow.run();
 
-                        // Request the DreamOverlay be told to dream with dream's window parameters
-                        // once the window has been attached.
-                        mOverlayConnection.request(overlay -> {
-                            try {
-                                overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
-                            } catch (RemoteException e) {
-                                Log.e(mTag, "could not send window attributes:" + e);
-                            }
-                        });
+                        if (mOverlayConnection != null) {
+                            // Request the DreamOverlay be told to dream with dream's window
+                            // parameters once the window has been attached.
+                            mDreamStartOverlayConsumer = overlay -> {
+                                try {
+                                    overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
+                                            mDreamComponent.flattenToString(),
+                                            mShouldShowComplications);
+                                } catch (RemoteException e) {
+                                    Log.e(mTag, "could not send window attributes:" + e);
+                                }
+                            };
+                            mOverlayConnection.addConsumer(mDreamStartOverlayConsumer);
+                        }
                     }
 
                     @Override
@@ -1362,6 +1373,9 @@
                             mActivity = null;
                             finish();
                         }
+                        if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
+                            mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
+                        }
                     }
                 });
     }
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 2b6633d..05ebbfe 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -31,7 +31,11 @@
     * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
                     token of the Dream Activity.
     * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
-    *                 dream.
+    *                dream.
+    * @param dreamComponent The component name of the dream service requesting overlay.
+    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+    *                and weather.
     */
-    void startDream(in LayoutParams params, in IDreamOverlayCallback callback);
+    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+        in String dreamComponent, in boolean shouldShowComplications);
 }
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index a2ca5a3..5d3852b 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -126,21 +126,26 @@
             Slog.d(TAG, "updateState()");
         }
         throwIfDetectorIsNoLongerActive();
-        synchronized (mLock) {
-            updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
+        try {
+            mManagerService.updateState(options, sharedMemory);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
-    protected void updateStateLocked(@Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback,
+    protected void initAndVerifyDetector(
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IHotwordRecognitionStatusCallback callback,
             int detectorType) {
         if (DEBUG) {
-            Slog.d(TAG, "updateStateLocked()");
+            Slog.d(TAG, "initAndVerifyDetector()");
         }
         Identity identity = new Identity();
         identity.packageName = ActivityThread.currentOpPackageName();
         try {
-            mManagerService.updateState(identity, options, sharedMemory, callback, detectorType);
+            mManagerService.initAndVerifyDetector(identity, options, sharedMemory, callback,
+                    detectorType);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d01e7fe..d58f6d3 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -817,10 +817,8 @@
 
     @Override
     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
-        // TODO: transition to use an API that is not updateState to provide
-        //  onHotwordDetectionServiceInitialized status to external callback
         if (mSupportHotwordDetectionService) {
-            updateStateLocked(options, sharedMemory, mInternalCallback,
+            initAndVerifyDetector(options, sharedMemory, mInternalCallback,
                     DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
         }
         try {
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 02561c94..11688df 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -69,9 +69,7 @@
 
     @Override
     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
-        // TODO: transition to use an API that is not updateState to provide
-        //  onHotwordDetectionServiceInitialized status to external callback
-        updateStateLocked(options, sharedMemory,
+        initAndVerifyDetector(options, sharedMemory,
                 new InitializationStateListener(mHandler, mCallback),
                 DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
     }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9fd8ecb..9b1d867 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -80,6 +80,7 @@
     private final boolean mHasButtonUnderPad;
     private final boolean mHasSensor;
     private final boolean mHasBattery;
+    private final boolean mSupportsUsi;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
     @GuardedBy("mMotionRanges")
@@ -462,7 +463,7 @@
             int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
             KeyCharacterMap keyCharacterMap, @InputDeviceCountryCode int countryCode,
             boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad,
-            boolean hasSensor, boolean hasBattery) {
+            boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
         mId = id;
         mGeneration = generation;
         mControllerNumber = controllerNumber;
@@ -481,6 +482,7 @@
         mHasSensor = hasSensor;
         mHasBattery = hasBattery;
         mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
+        mSupportsUsi = supportsUsi;
     }
 
     private InputDevice(Parcel in) {
@@ -501,6 +503,7 @@
         mHasButtonUnderPad = in.readInt() != 0;
         mHasSensor = in.readInt() != 0;
         mHasBattery = in.readInt() != 0;
+        mSupportsUsi = in.readInt() != 0;
         mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
 
         int numRanges = in.readInt();
@@ -538,6 +541,7 @@
         private boolean mHasBattery = false;
         @InputDeviceCountryCode
         private int mCountryCode = InputDeviceCountryCode.INVALID;
+        private boolean mSupportsUsi = false;
 
         /** @see InputDevice#getId()  */
         public Builder setId(int id) {
@@ -641,12 +645,18 @@
             return this;
         }
 
+        /** @see InputDevice#supportsUsi() ()  */
+        public Builder setSupportsUsi(boolean supportsUsi) {
+            mSupportsUsi = supportsUsi;
+            return this;
+        }
+
         /** Build {@link InputDevice}. */
         public InputDevice build() {
             return new InputDevice(mId, mGeneration, mControllerNumber, mName, mVendorId,
                     mProductId, mDescriptor, mIsExternal, mSources, mKeyboardType, mKeyCharacterMap,
                     mCountryCode, mHasVibrator, mHasMicrophone, mHasButtonUnderPad, mHasSensor,
-                    mHasBattery);
+                    mHasBattery, mSupportsUsi);
         }
     }
 
@@ -1179,6 +1189,15 @@
     }
 
     /**
+     * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for
+     * styluses.
+     * @hide
+     */
+    public boolean supportsUsi() {
+        return mSupportsUsi;
+    }
+
+    /**
      * Provides information about the range of values for a particular {@link MotionEvent} axis.
      *
      * @see InputDevice#getMotionRange(int)
@@ -1308,6 +1327,7 @@
         out.writeInt(mHasButtonUnderPad ? 1 : 0);
         out.writeInt(mHasSensor ? 1 : 0);
         out.writeInt(mHasBattery ? 1 : 0);
+        out.writeInt(mSupportsUsi ? 1 : 0);
 
         final int numRanges = mMotionRanges.size();
         out.writeInt(numRanges);
@@ -1361,6 +1381,8 @@
 
         description.append("  Has mic: ").append(mHasMicrophone).append("\n");
 
+        description.append("  Supports USI: ").append(mSupportsUsi).append("\n");
+
         description.append("  Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
         appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
         appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index eb8687c..7275780 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -59,6 +59,7 @@
     private static final int HAS_INSETS_SIZE_OVERRIDE = 2;
 
     private static Rect sTmpRect = new Rect();
+    private static Rect sTmpRect2 = new Rect();
 
     /**
      * The type of insets to provide.
@@ -88,6 +89,18 @@
      */
     public InsetsSizeOverride[] insetsSizeOverrides = null;
 
+    /**
+     * This field, if set, is indicating the insets needs to be at least the given size inside the
+     * display cutout safe area. This will be compared to the insets size calculated based on other
+     * attributes, and will be applied when this is larger. This is independent of the
+     * PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT in LayoutParams, as this is not going to change
+     * the layout of the window, but only change the insets frame. This can be applied to insets
+     * calculated based on all three source frames.
+     *
+     * Be cautious, this will not be in effect for the window types whose insets size is overridden.
+     */
+    public Insets minimalInsetsSizeInDisplayCutoutSafe = null;
+
     public InsetsFrameProvider(int type) {
         this(type, SOURCE_FRAME, null, null);
     }
@@ -202,7 +215,8 @@
 
     public static void calculateInsetsFrame(Rect displayFrame, Rect containerBounds,
             Rect displayCutoutSafe, Rect inOutFrame, int source, Insets insetsSize,
-            @WindowManager.LayoutParams.PrivateFlags int privateFlags) {
+            @WindowManager.LayoutParams.PrivateFlags int privateFlags,
+            Insets displayCutoutSafeInsetsSize) {
         boolean extendByCutout = false;
         if (source == InsetsFrameProvider.SOURCE_DISPLAY) {
             inOutFrame.set(displayFrame);
@@ -214,6 +228,33 @@
         if (insetsSize == null) {
             return;
         }
+        if (displayCutoutSafeInsetsSize != null) {
+            sTmpRect2.set(inOutFrame);
+        }
+        calculateInsetsFrame(inOutFrame, insetsSize);
+
+        if (extendByCutout) {
+            WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect);
+        }
+
+        if (displayCutoutSafeInsetsSize != null) {
+            // The insets is at least with the given size within the display cutout safe area.
+            // Calculate the smallest size.
+            calculateInsetsFrame(sTmpRect2, displayCutoutSafeInsetsSize);
+            WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, sTmpRect2, sTmpRect);
+            // If it's larger than previous calculation, use it.
+            if (sTmpRect2.contains(inOutFrame)) {
+                inOutFrame.set(sTmpRect2);
+            }
+        }
+    }
+
+    /**
+     * Calculate the insets frame given the insets size and the source frame.
+     * @param inOutFrame the source frame.
+     * @param insetsSize the insets size. Only the first non-zero value will be taken.
+     */
+    private static void calculateInsetsFrame(Rect inOutFrame, Insets insetsSize) {
         // Only one side of the provider shall be applied. Check in the order of left - top -
         // right - bottom, only the first non-zero value will be applied.
         if (insetsSize.left != 0) {
@@ -227,10 +268,6 @@
         } else {
             inOutFrame.setEmpty();
         }
-
-        if (extendByCutout) {
-            WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect);
-        }
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c899e4..7b6ebf7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15905,15 +15905,16 @@
     }
 
     /**
-     * Returns whether the device is currently in touch mode. Touch mode is entered
-     * once the user begins interacting with the device by touch, and affects various
-     * things like whether focus is always visible to the user.
+     * Returns the touch mode state associated with this view.
      *
-     * If this view has no {@link ViewRootImpl} or {@link Display} attached, then it will return
-     * the default touch mode value defined in
+     * Attached views return the touch mode state from the associated window's display.
+     * Detached views just return the default touch mode value defined in
      * {@code com.android.internal.R.bool.config_defaultInTouchMode}.
      *
-     * @return Whether the device is in touch mode.
+     * Touch mode is entered once the user begins interacting with the device by touch, and
+     * affects various things like whether focus highlight is always visible to the user.
+     *
+     * @return the touch mode state associated with this view
      */
     @ViewDebug.ExportedProperty
     public boolean isInTouchMode() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1e2b241..b2b5f13 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1101,7 +1101,7 @@
 
         // Update the last resource config in case the resource configuration was changed while
         // activity relaunched.
-        mLastConfigurationFromResources.setTo(getConfiguration());
+        updateLastConfigurationFromResources(getConfiguration());
     }
 
     private Configuration getConfiguration() {
@@ -5424,13 +5424,7 @@
             // Update the display with new DisplayAdjustments.
             updateInternalDisplay(mDisplay.getDisplayId(), localResources);
 
-            final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
-            final int currentLayoutDirection = config.getLayoutDirection();
-            mLastConfigurationFromResources.setTo(config);
-            if (lastLayoutDirection != currentLayoutDirection
-                    && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
-                mView.setLayoutDirection(currentLayoutDirection);
-            }
+            updateLastConfigurationFromResources(config);
             mView.dispatchConfigurationChanged(config);
 
             // We could have gotten this {@link Configuration} update after we called
@@ -5444,6 +5438,17 @@
         updateForceDarkMode();
     }
 
+    private void updateLastConfigurationFromResources(Configuration resConfig) {
+        final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
+        final int currentLayoutDirection = resConfig.getLayoutDirection();
+        mLastConfigurationFromResources.setTo(resConfig);
+        // Update layout direction in case the language or screen layout is changed.
+        if (lastLayoutDirection != currentLayoutDirection && mView != null
+                && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+            mView.setLayoutDirection(currentLayoutDirection);
+        }
+    }
+
     /**
      * Return true if child is an ancestor of parent, (or equal to the parent).
      */
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index dbefcfb..70cfc3e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -543,7 +543,6 @@
      *  {@link com.android.server.autofill.PresentationStatsEventLogger#getNoPresentationEventReason(int)}
      *  as well.</p>
      *
-     *  TODO(b/233833662): Expose this as a public API in U.
      *  @hide
      */
     @IntDef(prefix = { "COMMIT_REASON_" }, value = {
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index bbcf982..9f23f24 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -245,6 +245,23 @@
 
     /**
      * Set configuration and pass read-only data to hotword detection service.
+     *
+     * @param options Application configuration data to provide to the
+     * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to provide to the
+     * {@link HotwordDetectionService}. Use this to provide the hotword models data or other
+     * such data to the trusted process.
+     */
+    @EnforcePermission("MANAGE_HOTWORD_DETECTION")
+    void updateState(
+            in PersistableBundle options,
+            in SharedMemory sharedMemory);
+
+    /**
+     * Set configuration and pass read-only data to hotword detection service when creating
+     * the detector.
+     *
      * Caller must provide an identity, used for permission tracking purposes.
      * The uid/pid elements of the identity will be ignored by the server and replaced with the ones
      * provided by binder.
@@ -259,7 +276,7 @@
      * @param detectorType Indicate which detector is used.
      */
     @EnforcePermission("MANAGE_HOTWORD_DETECTION")
-    void updateState(
+    void initAndVerifyDetector(
             in Identity originatorIdentity,
             in PersistableBundle options,
             in SharedMemory sharedMemory,
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
new file mode 100644
index 0000000..3165d29
--- /dev/null
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2022 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.internal.util;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CallbackRegistry.NotifierCallback;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that enables monitoring the status of a binder connection. It also aides in automatically
+ * converting a proxy into an internal wrapper type.
+ *
+ * @param <T> The type of the wrapper over the resulting service.
+ */
+public class ObservableServiceConnection<T> implements ServiceConnection {
+    /**
+     * An interface for converting the service proxy into a given internal wrapper type.
+     *
+     * @param <T> The type of the wrapper over the resulting service.
+     */
+    public interface ServiceTransformer<T> {
+        /**
+         * Called to convert the service proxy to the wrapper type.
+         *
+         * @param service The service proxy to create the wrapper type from.
+         * @return The wrapper type.
+         */
+        T convert(IBinder service);
+    }
+
+    /**
+     * An interface for listening to the connection status.
+     *
+     * @param <T> The wrapper type.
+     */
+    public interface Callback<T> {
+        /**
+         * Invoked when the service has been successfully connected to.
+         *
+         * @param connection The {@link ObservableServiceConnection} instance that is now connected
+         * @param service    The service proxy converted into the typed wrapper.
+         */
+        void onConnected(ObservableServiceConnection<T> connection, T service);
+
+        /**
+         * Invoked when the service has been disconnected.
+         *
+         * @param connection The {@link ObservableServiceConnection} that is now disconnected.
+         * @param reason     The reason for the disconnection.
+         */
+        void onDisconnected(ObservableServiceConnection<T> connection,
+                @DisconnectReason int reason);
+    }
+
+    /**
+     * Default state, service has not yet disconnected.
+     */
+    public static final int DISCONNECT_REASON_NONE = 0;
+    /**
+     * Disconnection was due to the resulting binding being {@code null}.
+     */
+    public static final int DISCONNECT_REASON_NULL_BINDING = 1;
+    /**
+     * Disconnection was due to the remote end disconnecting.
+     */
+    public static final int DISCONNECT_REASON_DISCONNECTED = 2;
+    /**
+     * Disconnection due to the binder dying.
+     */
+    public static final int DISCONNECT_REASON_BINDING_DIED = 3;
+    /**
+     * Disconnection from an explicit unbinding.
+     */
+    public static final int DISCONNECT_REASON_UNBIND = 4;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DISCONNECT_REASON_NONE,
+            DISCONNECT_REASON_NULL_BINDING,
+            DISCONNECT_REASON_DISCONNECTED,
+            DISCONNECT_REASON_BINDING_DIED,
+            DISCONNECT_REASON_UNBIND
+    })
+    public @interface DisconnectReason {
+    }
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final Executor mExecutor;
+    private final ServiceTransformer<T> mTransformer;
+    private final Intent mServiceIntent;
+    private final int mFlags;
+
+    @GuardedBy("mLock")
+    private T mService;
+    @GuardedBy("mLock")
+    private boolean mBoundCalled = false;
+    @GuardedBy("mLock")
+    private int mLastDisconnectReason = DISCONNECT_REASON_NONE;
+
+    private final CallbackRegistry<Callback<T>, ObservableServiceConnection<T>, T>
+            mCallbackRegistry = new CallbackRegistry<>(
+            new NotifierCallback<Callback<T>, ObservableServiceConnection<T>, T>() {
+                    @Override
+                    public void onNotifyCallback(Callback<T> callback,
+                            ObservableServiceConnection<T> sender,
+                            int disconnectReason, T service) {
+                        mExecutor.execute(() -> {
+                            synchronized (mLock) {
+                                if (service != null) {
+                                    callback.onConnected(sender, service);
+                                } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+                                    callback.onDisconnected(sender, disconnectReason);
+                                }
+                            }
+                        });
+                    }
+                });
+
+    /**
+     * Default constructor for {@link ObservableServiceConnection}.
+     *
+     * @param context     The context from which the service will be bound with.
+     * @param executor    The executor for connection callbacks to be delivered on
+     * @param transformer A {@link ObservableServiceConnection.ServiceTransformer} for transforming
+     *                    the resulting service into a desired type.
+     */
+    public ObservableServiceConnection(@NonNull Context context,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ServiceTransformer<T> transformer,
+            Intent serviceIntent,
+            int flags) {
+        mContext = context;
+        mExecutor = executor;
+        mTransformer = transformer;
+        mServiceIntent = serviceIntent;
+        mFlags = flags;
+    }
+
+    /**
+     * Initiate binding to the service.
+     *
+     * @return {@code true} if initiating binding succeed, {@code false} if the binding failed or
+     * if this service is already bound. Regardless of the return value, you should later call
+     * {@link #unbind()} to release the connection.
+     */
+    public boolean bind() {
+        synchronized (mLock) {
+            if (mBoundCalled) {
+                return false;
+            }
+            final boolean bindResult =
+                    mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+            mBoundCalled = true;
+            return bindResult;
+        }
+    }
+
+    /**
+     * Disconnect from the service if bound.
+     */
+    public void unbind() {
+        onDisconnected(DISCONNECT_REASON_UNBIND);
+    }
+
+    /**
+     * Adds a callback for receiving connection updates.
+     *
+     * @param callback The {@link Callback} to receive future updates.
+     */
+    public void addCallback(Callback<T> callback) {
+        mCallbackRegistry.add(callback);
+        mExecutor.execute(() -> {
+            synchronized (mLock) {
+                if (mService != null) {
+                    callback.onConnected(this, mService);
+                } else if (mLastDisconnectReason != DISCONNECT_REASON_NONE) {
+                    callback.onDisconnected(this, mLastDisconnectReason);
+                }
+            }
+        });
+    }
+
+    /**
+     * Removes previously added callback from receiving future connection updates.
+     *
+     * @param callback The {@link Callback} to be removed.
+     */
+    public void removeCallback(Callback<T> callback) {
+        synchronized (mLock) {
+            mCallbackRegistry.remove(callback);
+        }
+    }
+
+    private void onDisconnected(@DisconnectReason int reason) {
+        synchronized (mLock) {
+            if (!mBoundCalled) {
+                return;
+            }
+            mBoundCalled = false;
+            mLastDisconnectReason = reason;
+            mContext.unbindService(this);
+            mService = null;
+            mCallbackRegistry.notifyCallbacks(this, reason, null);
+        }
+    }
+
+    @Override
+    public final void onServiceConnected(ComponentName name, IBinder service) {
+        synchronized (mLock) {
+            mService = mTransformer.convert(service);
+            mLastDisconnectReason = DISCONNECT_REASON_NONE;
+            mCallbackRegistry.notifyCallbacks(this, mLastDisconnectReason, mService);
+        }
+    }
+
+    @Override
+    public final void onServiceDisconnected(ComponentName name) {
+        onDisconnected(DISCONNECT_REASON_DISCONNECTED);
+    }
+
+    @Override
+    public final void onBindingDied(ComponentName name) {
+        onDisconnected(DISCONNECT_REASON_BINDING_DIED);
+    }
+
+    @Override
+    public final void onNullBinding(ComponentName name) {
+        onDisconnected(DISCONNECT_REASON_NULL_BINDING);
+    }
+}
diff --git a/core/java/com/android/internal/util/PersistentServiceConnection.java b/core/java/com/android/internal/util/PersistentServiceConnection.java
new file mode 100644
index 0000000..d201734
--- /dev/null
+++ b/core/java/com/android/internal/util/PersistentServiceConnection.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 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.internal.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+
+/**
+ * {@link PersistentServiceConnection} is a concrete implementation of {@link ServiceConnection}
+ * that maintains the binder connection by handling reconnection when a failure occurs.
+ *
+ * @param <T> The transformed connection type handled by the service.
+ *
+ * When the target process is killed (by OOM-killer, force-stopped, crash, etc..) then this class
+ * will trigger a reconnection to the target. This should be used carefully.
+ *
+ * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
+ * the target package being updated, this class won't reconnect.  This is because this class doesn't
+ * know what to do when the service component has gone missing, for example.  If the user of this
+ * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
+ * explicitly.
+ */
+public class PersistentServiceConnection<T> extends ObservableServiceConnection<T> {
+    private final Callback<T> mConnectionCallback = new Callback<T>() {
+        private long mConnectedTime;
+
+        @Override
+        public void onConnected(ObservableServiceConnection<T> connection, T service) {
+            mConnectedTime = mInjector.uptimeMillis();
+        }
+
+        @Override
+        public void onDisconnected(ObservableServiceConnection<T> connection,
+                @DisconnectReason int reason) {
+            if (reason == DISCONNECT_REASON_UNBIND) return;
+            synchronized (mLock) {
+                if ((mInjector.uptimeMillis() - mConnectedTime) > mMinConnectionDurationMs) {
+                    mReconnectAttempts = 0;
+                    bindInternalLocked();
+                } else {
+                    scheduleConnectionAttemptLocked();
+                }
+            }
+        }
+    };
+
+    private final Object mLock = new Object();
+    private final Injector mInjector;
+    private final Handler mHandler;
+    private final int mMinConnectionDurationMs;
+    private final int mMaxReconnectAttempts;
+    private final int mBaseReconnectDelayMs;
+    @GuardedBy("mLock")
+    private int mReconnectAttempts;
+    @GuardedBy("mLock")
+    private Object mCancelToken;
+
+    private final Runnable mConnectRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                mCancelToken = null;
+                bindInternalLocked();
+            }
+        }
+    };
+
+    /**
+     * Default constructor for {@link PersistentServiceConnection}.
+     *
+     * @param context     The context from which the service will be bound with.
+     * @param executor    The executor for connection callbacks to be delivered on
+     * @param transformer A {@link ServiceTransformer} for transforming
+     */
+    public PersistentServiceConnection(Context context,
+            Executor executor,
+            Handler handler,
+            ServiceTransformer<T> transformer,
+            Intent serviceIntent,
+            int flags,
+            int minConnectionDurationMs,
+            int maxReconnectAttempts,
+            int baseReconnectDelayMs) {
+        this(context,
+                executor,
+                handler,
+                transformer,
+                serviceIntent,
+                flags,
+                minConnectionDurationMs,
+                maxReconnectAttempts,
+                baseReconnectDelayMs,
+                new Injector());
+    }
+
+    @VisibleForTesting
+    public PersistentServiceConnection(
+            Context context,
+            Executor executor,
+            Handler handler,
+            ServiceTransformer<T> transformer,
+            Intent serviceIntent,
+            int flags,
+            int minConnectionDurationMs,
+            int maxReconnectAttempts,
+            int baseReconnectDelayMs,
+            Injector injector) {
+        super(context, executor, transformer, serviceIntent, flags);
+        mHandler = handler;
+        mMinConnectionDurationMs = minConnectionDurationMs;
+        mMaxReconnectAttempts = maxReconnectAttempts;
+        mBaseReconnectDelayMs = baseReconnectDelayMs;
+        mInjector = injector;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean bind() {
+        synchronized (mLock) {
+            addCallback(mConnectionCallback);
+            mReconnectAttempts = 0;
+            return bindInternalLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean bindInternalLocked() {
+        return super.bind();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unbind() {
+        synchronized (mLock) {
+            removeCallback(mConnectionCallback);
+            cancelPendingConnectionAttemptLocked();
+            super.unbind();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cancelPendingConnectionAttemptLocked() {
+        if (mCancelToken != null) {
+            mHandler.removeCallbacksAndMessages(mCancelToken);
+            mCancelToken = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleConnectionAttemptLocked() {
+        cancelPendingConnectionAttemptLocked();
+
+        if (mReconnectAttempts >= mMaxReconnectAttempts) {
+            return;
+        }
+
+        final long reconnectDelayMs =
+                (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+        mCancelToken = new Object();
+        mHandler.postDelayed(mConnectRunnable, mCancelToken, reconnectDelayMs);
+        mReconnectAttempts++;
+    }
+
+    /**
+     * Injector for testing
+     */
+    @VisibleForTesting
+    public static class Injector {
+        /**
+         * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
+         * in tests with a fake clock.
+         */
+        public long uptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index af9c5a5..52ffc98 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -17,6 +17,9 @@
 
 package com.android.internal.widget;
 
+import static android.content.res.Resources.ID_NULL;
+
+import android.annotation.IdRes;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
@@ -96,6 +99,8 @@
 
     private int mTopOffset;
     private boolean mShowAtTop;
+    @IdRes
+    private int mIgnoreOffsetTopLimitViewId = ID_NULL;
 
     private boolean mIsDragging;
     private boolean mOpenOnClick;
@@ -156,6 +161,10 @@
         mIsMaxCollapsedHeightSmallExplicit =
                 a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall);
         mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
+        if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) {
+            mIgnoreOffsetTopLimitViewId = a.getResourceId(
+                    R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL);
+        }
         a.recycle();
 
         mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
@@ -577,12 +586,32 @@
                 dy -= 1.0f;
             }
 
+            boolean isIgnoreOffsetLimitSet = false;
+            int ignoreOffsetLimit = 0;
+            View ignoreOffsetLimitView = findIgnoreOffsetLimitView();
+            if (ignoreOffsetLimitView != null) {
+                LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams();
+                ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin;
+                isIgnoreOffsetLimitSet = true;
+            }
             final int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 final View child = getChildAt(i);
+                if (child.getVisibility() == View.GONE) {
+                    continue;
+                }
                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 if (!lp.ignoreOffset) {
                     child.offsetTopAndBottom((int) dy);
+                } else if (isIgnoreOffsetLimitSet) {
+                    int top = child.getTop();
+                    int targetTop = Math.max(
+                            (int) (ignoreOffsetLimit + lp.topMargin + dy),
+                            lp.mFixedTop);
+                    if (top != targetTop) {
+                        child.offsetTopAndBottom(targetTop - top);
+                    }
+                    ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
                 }
             }
             final boolean isCollapsedOld = mCollapseOffset != 0;
@@ -1024,6 +1053,8 @@
         final int rightEdge = width - getPaddingRight();
         final int widthAvailable = rightEdge - leftEdge;
 
+        boolean isIgnoreOffsetLimitSet = false;
+        int ignoreOffsetLimit = 0;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final View child = getChildAt(i);
@@ -1036,9 +1067,24 @@
                 continue;
             }
 
+            if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) {
+                if (mIgnoreOffsetTopLimitViewId == child.getId()) {
+                    ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
+                    isIgnoreOffsetLimitSet = true;
+                }
+            }
+
             int top = ypos + lp.topMargin;
             if (lp.ignoreOffset) {
-                top -= mCollapseOffset;
+                if (!isDragging()) {
+                    lp.mFixedTop = (int) (top - mCollapseOffset);
+                }
+                if (isIgnoreOffsetLimitSet) {
+                    top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset));
+                    ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin;
+                } else {
+                    top -= mCollapseOffset;
+                }
             }
             final int bottom = top + child.getMeasuredHeight();
 
@@ -1102,11 +1148,23 @@
         mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
     }
 
+    private View findIgnoreOffsetLimitView() {
+        if (mIgnoreOffsetTopLimitViewId == ID_NULL) {
+            return null;
+        }
+        View v = findViewById(mIgnoreOffsetTopLimitViewId);
+        if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) {
+            return v;
+        }
+        return null;
+    }
+
     public static class LayoutParams extends MarginLayoutParams {
         public boolean alwaysShow;
         public boolean ignoreOffset;
         public boolean hasNestedScrollIndicator;
         public int maxHeight;
+        int mFixedTop;
 
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index bb4ab39..1f64df4 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -101,9 +101,18 @@
 static void android_os_Parcel_markForBinder(JNIEnv* env, jclass clazz, jlong nativePtr,
                                             jobject binder)
 {
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "Null binder specified for markForBinder");
+
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel) {
-        parcel->markForBinder(ibinderForJavaObject(env, binder));
+        sp<IBinder> nBinder = ibinderForJavaObject(env, binder);
+
+        if (nBinder == nullptr) {
+            ALOGE("Native binder in markForBinder is null for non-null jobject");
+            return;
+        }
+
+        parcel->markForBinder(nBinder);
     }
 }
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f28e2f6..9f88f33 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -798,6 +798,12 @@
     if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
         JavaBBinderHolder* jbh = (JavaBBinderHolder*)
             env->GetLongField(obj, gBinderOffsets.mObject);
+
+        if (jbh == nullptr) {
+            ALOGE("JavaBBinderHolder null on binder");
+            return nullptr;
+        }
+
         return jbh->get(env, obj);
     }
 
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index aece8c3..39ec037 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -57,9 +57,6 @@
 
     const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
 
-    // Not sure why, but JNI is complaining when I pass this through directly.
-    jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE;
-
     ScopedLocalRef<jobject>
             inputDeviceObj(env,
                            env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor,
@@ -70,8 +67,9 @@
                                           deviceInfo.isExternal(), deviceInfo.getSources(),
                                           deviceInfo.getKeyboardType(), kcmObj.get(),
                                           deviceInfo.getCountryCode(), deviceInfo.hasVibrator(),
-                                          hasMic, deviceInfo.hasButtonUnderPad(),
-                                          deviceInfo.hasSensor(), deviceInfo.hasBattery()));
+                                          deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
+                                          deviceInfo.hasSensor(), deviceInfo.hasBattery(),
+                                          deviceInfo.supportsUsi()));
 
     const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -94,7 +92,7 @@
     gInputDeviceClassInfo.ctor =
             GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
                              "(IIILjava/lang/String;IILjava/lang/"
-                             "String;ZIILandroid/view/KeyCharacterMap;IZZZZZ)V");
+                             "String;ZIILandroid/view/KeyCharacterMap;IZZZZZZ)V");
 
     gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6a200d05..a06e8a4 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -23,6 +23,7 @@
     android:maxWidth="@dimen/resolver_max_width"
     android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
     android:maxCollapsedHeightSmall="56dp"
+    android:ignoreOffsetTopLimit="@id/title_container"
     android:id="@id/contentPanel">
 
     <RelativeLayout
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 69c5043..2d832bc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9620,6 +9620,12 @@
         <attr name="maxCollapsedHeightSmall" format="dimension" />
         <!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->
         <attr name="showAtTop" format="boolean" />
+        <!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property
+             set to true have a fixed position in the layout that won’t be affected by the drawer’s
+             movements. This property alternates that behavior. It specifies a child view’s id that
+             will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the
+             top limit the ignoreOffset elements. -->
+        <attr name="ignoreOffsetTopLimit" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="MessagingLinearLayout">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 47faf2a..3834478 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -553,6 +553,14 @@
     <!-- If this is true, then keep dreaming when undocking. -->
     <bool name="config_keepDreamingWhenUndocking">false</bool>
 
+    <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
+         it becomes disconnected -->
+    <integer name="config_dreamOverlayReconnectTimeoutMs">1000</integer> <!-- 1 second -->
+    <!-- The maximum number of times to attempt reconnecting to the dream overlay service -->
+    <integer name="config_dreamOverlayMaxReconnectAttempts">3</integer>
+    <!-- The duration after which the dream overlay connection should be considered stable -->
+    <integer name="config_minDreamOverlayDurationMs">10000</integer> <!-- 10 seconds -->
+
     <!-- Auto-rotation behavior -->
 
     <!-- If true, enables auto-rotation features using the accelerometer.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b42db13..530891f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1626,6 +1626,7 @@
   <java-symbol type="xml" name="password_kbd_symbols_shift" />
   <java-symbol type="xml" name="power_profile" />
   <java-symbol type="xml" name="power_profile_test" />
+  <java-symbol type="xml" name="irq_device_map" />
   <java-symbol type="xml" name="sms_short_codes" />
   <java-symbol type="xml" name="audio_assets" />
   <java-symbol type="xml" name="global_keys" />
@@ -2238,6 +2239,9 @@
   <java-symbol type="array" name="config_supportedDreamComplications" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
+  <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
+  <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
+  <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
   <java-symbol type="string" name="config_loggable_dream_prefix" />
   <java-symbol type="string" name="config_dozeComponent" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
new file mode 100644
index 0000000..86a44d6
--- /dev/null
+++ b/core/res/res/xml/irq_device_map.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+<irq-device-map>
+  <!--  This file maps devices (chips) that can send IRQs to the CPU (and bring it out of sleep) to
+        logical subsystems in userspace code. Since each Android device has its own uniquely
+        designed chipset, this mapping is expected to be empty by default and should be overridden
+        by device specific configs.
+        This mapping helps the system to meaningfully attribute CPU wakeups to logical work that
+        happened on the device. The devices are referred to by their names as defined in the kernel.
+        Currently defined subsystems are:
+        - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
+
+        The overlay should use tags <device> and <subsystem> to describe this mapping in the
+        following way:
+
+        <irq-device-map>
+            <device name="device_name_1">
+                <subsystem>Subsystem1</subsystem>
+                <subsystem>Subsystem2</subsystem>
+                :
+                :
+            </device>
+            <device name="device_name_2">
+                :
+            </device>
+            :
+        </irq-device-map>
+
+        The tag <device> should have a "name" attribute specifying the kernel name of the device.
+        Each <device> tag can then enclose multiple <subsystem> tags. Each <subsystem> tag should
+        enclose the name of the logical subsystems (one of the ones defined above) as text.
+        Undefined subsystem names will be ignored by the framework.
+  -->
+</irq-device-map>
\ No newline at end of file
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 6bd498c..d633843 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -124,7 +124,7 @@
         assertTransportBehavior(input, expected);
     }
 
-    public void testMutiplePingPing() {
+    public void testMultiplePingPing() {
         final byte[] input = concat(
                 generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"),
                 generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
@@ -248,7 +248,7 @@
         }
 
         @Override
-        public int read(byte b[], int off, int len) throws IOException {
+        public int read(byte[] b, int off, int len) throws IOException {
             // Instead of hanging indefinitely, wait a bit and claim that
             // nothing was read, without hitting EOF
             SystemClock.sleep(100);
@@ -259,13 +259,13 @@
     private static class DelayingInputStream extends FilterInputStream {
         private final long mDelay;
 
-        public DelayingInputStream(InputStream in, long delay) {
+        DelayingInputStream(InputStream in, long delay) {
             super(in);
             mDelay = delay;
         }
 
         @Override
-        public int read(byte b[], int off, int len) throws IOException {
+        public int read(byte[] b, int off, int len) throws IOException {
             SystemClock.sleep(mDelay);
             return super.read(b, off, len);
         }
@@ -274,25 +274,25 @@
     private static class DelayingOutputStream extends FilterOutputStream {
         private final long mDelay;
 
-        public DelayingOutputStream(OutputStream out, long delay) {
+        DelayingOutputStream(OutputStream out, long delay) {
             super(out);
             mDelay = delay;
         }
 
         @Override
-        public void write(byte b[], int off, int len) throws IOException {
+        public void write(byte[] b, int off, int len) throws IOException {
             SystemClock.sleep(mDelay);
             super.write(b, off, len);
         }
     }
 
     private static class TrickleInputStream extends FilterInputStream {
-        public TrickleInputStream(InputStream in) {
+        TrickleInputStream(InputStream in) {
             super(in);
         }
 
         @Override
-        public int read(byte b[], int off, int len) throws IOException {
+        public int read(byte[] b, int off, int len) throws IOException {
             return super.read(b, off, 1);
         }
     }
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e154586..f6216fa 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1654,6 +1654,12 @@
             android:exported="true">
         </activity>
 
+        <activity android:name="android.app.activity.ActivityTransitionDrawableTest$TestActivity"
+                  android:exported="true"
+                  android:enabled="true"
+                  android:theme="@style/Theme">
+        </activity>
+
         <activity
             android:name="android.os.TestVrActivity"
             android:enableVrMode="com.android.frameworks.coretests/android.os.TestVrActivity$TestVrListenerService">
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java b/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java
new file mode 100644
index 0000000..2c4e443
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ActivityTransitionDrawableTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2022 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 android.app.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.transition.Fade;
+import android.view.View;
+import android.view.Window;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for verifying Activity Transitions Drawable behavior
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class ActivityTransitionDrawableTest {
+    private static final String LAUNCH_ON_START = "launch on start";
+
+    @Rule
+    public final ActivityTestRule<TestActivity> mActivityTestRule =
+            new ActivityTestRule<>(TestActivity.class, true);
+
+    @Test
+    public void stopTransitionDrawableAlphaRestored() throws Throwable {
+        mActivityTestRule.runOnUiThread(() -> {
+            Activity activity = mActivityTestRule.getActivity();
+            Intent intent = new Intent(activity, TestActivity.class);
+            intent.putExtra(LAUNCH_ON_START, true);
+            Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
+            activity.startActivity(intent, bundle);
+        });
+
+        assertThat(TestActivity.activityAdded.await(5, TimeUnit.SECONDS)).isTrue();
+        TestActivity topActivity = TestActivity.sInstances.get(2);
+        TestActivity middleActivity = TestActivity.sInstances.get(1);
+        assertThat(topActivity.startedLatch.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(middleActivity.stoppedLatch.await(5, TimeUnit.SECONDS)).isTrue();
+        mActivityTestRule.runOnUiThread(() -> {
+            assertThat(middleActivity.getWindow().getDecorView().getBackground().getAlpha())
+                    .isEqualTo(255);
+        });
+    }
+
+    public static class TestActivity extends Activity {
+        public static final ArrayList<TestActivity> sInstances = new ArrayList<TestActivity>();
+        public static CountDownLatch activityAdded = new CountDownLatch(3);
+
+        private boolean mLaunchOnStart = false;
+        public CountDownLatch startedLatch = new CountDownLatch(1);
+        public CountDownLatch stoppedLatch = new CountDownLatch(1);
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+            getWindow().setAllowEnterTransitionOverlap(false);
+            setContentView(new View(this));
+            Fade longFade = new Fade();
+            longFade.setDuration(2000);
+            getWindow().setEnterTransition(longFade);
+            getWindow().setExitTransition(longFade);
+            super.onCreate(savedInstanceState);
+            mLaunchOnStart = getIntent().getBooleanExtra(LAUNCH_ON_START, false);
+            sInstances.add(this);
+            activityAdded.countDown();
+        }
+
+        @Override
+        protected void onStart() {
+            super.onStart();
+            if (mLaunchOnStart) {
+                mLaunchOnStart = false;
+                Intent intent = new Intent(this, TestActivity.class);
+                startActivity(intent);
+            }
+            startedLatch.countDown();
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            stoppedLatch.countDown();
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            sInstances.remove(this);
+        }
+    }
+}
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index adc3676..3798da5 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -34,6 +34,7 @@
         "mockito-target-minus-junit4",
         "androidx.test.ext.junit",
         "truth-prebuilt",
+        "servicestests-utils",
     ],
 
     libs: [
diff --git a/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
new file mode 100644
index 0000000..d124ad9
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ObservableServiceConnectionTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 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.internal.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+@SmallTest
+public class ObservableServiceConnectionTest {
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("test.package", "component");
+
+    public static class Foo {
+        int mValue;
+
+        Foo(int value) {
+            mValue = value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Foo)) return false;
+            Foo foo = (Foo) o;
+            return mValue == foo.mValue;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mValue);
+        }
+    }
+
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Intent mIntent;
+    @Mock
+    private Foo mResult;
+    @Mock
+    private IBinder mBinder;
+    @Mock
+    private ServiceTransformer<Foo> mTransformer;
+    @Mock
+    private ObservableServiceConnection.Callback<Foo> mCallback;
+    private final FakeExecutor mExecutor = new FakeExecutor();
+    private ObservableServiceConnection<Foo> mConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mConnection = new ObservableServiceConnection<>(
+                mContext,
+                mExecutor,
+                mTransformer,
+                mIntent,
+                /* flags= */ Context.BIND_AUTO_CREATE);
+    }
+
+    @After
+    public void tearDown() {
+        mExecutor.clearAll();
+    }
+
+    @Test
+    public void testConnect() {
+        // Register twice to ensure only one callback occurs.
+        mConnection.addCallback(mCallback);
+        mConnection.addCallback(mCallback);
+
+        mExecutor.runAll();
+        mConnection.bind();
+
+        // Ensure that no callbacks happen before connection.
+        verify(mCallback, never()).onConnected(any(), any());
+        verify(mCallback, never()).onDisconnected(any(), anyInt());
+
+        when(mTransformer.convert(mBinder)).thenReturn(mResult);
+        mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+
+        mExecutor.runAll();
+        verify(mCallback, times(1)).onConnected(mConnection, mResult);
+    }
+
+    @Test
+    public void testDisconnectBeforeBind() {
+        mConnection.addCallback(mCallback);
+        mExecutor.runAll();
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+        mExecutor.runAll();
+        // Disconnects before binds should be ignored.
+        verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+    }
+
+    @Test
+    public void testDisconnect() {
+        mConnection.addCallback(mCallback);
+        mExecutor.runAll();
+        mConnection.bind();
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+
+        // Ensure the callback doesn't get triggered until the executor runs.
+        verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+        mExecutor.runAll();
+        // Ensure proper disconnect reason reported.
+        verify(mCallback, times(1)).onDisconnected(mConnection,
+                ObservableServiceConnection.DISCONNECT_REASON_DISCONNECTED);
+        // Verify unbound from service.
+        verify(mContext, times(1)).unbindService(mConnection);
+
+        clearInvocations(mContext);
+        // Ensure unbind after disconnect has no effect on the connection
+        mConnection.unbind();
+        verify(mContext, never()).unbindService(mConnection);
+    }
+
+    @Test
+    public void testBindingDied() {
+        mConnection.addCallback(mCallback);
+        mExecutor.runAll();
+        mConnection.bind();
+        mConnection.onBindingDied(COMPONENT_NAME);
+
+        // Ensure the callback doesn't get triggered until the executor runs.
+        verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+        mExecutor.runAll();
+        // Ensure proper disconnect reason reported.
+        verify(mCallback, times(1)).onDisconnected(mConnection,
+                ObservableServiceConnection.DISCONNECT_REASON_BINDING_DIED);
+        // Verify unbound from service.
+        verify(mContext, times(1)).unbindService(mConnection);
+    }
+
+    @Test
+    public void testNullBinding() {
+        mConnection.addCallback(mCallback);
+        mExecutor.runAll();
+        mConnection.bind();
+        mConnection.onNullBinding(COMPONENT_NAME);
+
+        // Ensure the callback doesn't get triggered until the executor runs.
+        verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+        mExecutor.runAll();
+        // Ensure proper disconnect reason reported.
+        verify(mCallback, times(1)).onDisconnected(mConnection,
+                ObservableServiceConnection.DISCONNECT_REASON_NULL_BINDING);
+        // Verify unbound from service.
+        verify(mContext, times(1)).unbindService(mConnection);
+    }
+
+    @Test
+    public void testUnbind() {
+        mConnection.addCallback(mCallback);
+        mExecutor.runAll();
+        mConnection.bind();
+        mConnection.unbind();
+
+        // Ensure the callback doesn't get triggered until the executor runs.
+        verify(mCallback, never()).onDisconnected(eq(mConnection), anyInt());
+        mExecutor.runAll();
+        verify(mCallback).onDisconnected(mConnection,
+                ObservableServiceConnection.DISCONNECT_REASON_UNBIND);
+    }
+
+    static class FakeExecutor implements Executor {
+        private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+        @Override
+        public void execute(Runnable command) {
+            mQueue.add(command);
+        }
+
+        public void runAll() {
+            while (!mQueue.isEmpty()) {
+                mQueue.remove().run();
+            }
+        }
+
+        public void clearAll() {
+            while (!mQueue.isEmpty()) {
+                mQueue.remove();
+            }
+        }
+    }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
new file mode 100644
index 0000000..fee4654
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/PersistentServiceConnectionTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 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.internal.util;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.internal.util.ObservableServiceConnection.ServiceTransformer;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+public class PersistentServiceConnectionTest {
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("test.package", "component");
+    private static final int MAX_RETRIES = 2;
+    private static final int RETRY_DELAY_MS = 1000;
+    private static final int CONNECTION_MIN_DURATION_MS = 5000;
+    private PersistentServiceConnection<Proxy> mConnection;
+
+    public static class Proxy {
+    }
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Intent mIntent;
+    @Mock
+    private Proxy mResult;
+    @Mock
+    private IBinder mBinder;
+    @Mock
+    private ServiceTransformer<Proxy> mTransformer;
+    @Mock
+    private ObservableServiceConnection.Callback<Proxy> mCallback;
+    private TestHandler mHandler;
+    private final FakeExecutor mFakeExecutor = new FakeExecutor();
+    private OffsettableClock mClock;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mClock = new OffsettableClock.Stopped();
+        mHandler = spy(new TestHandler(null, mClock));
+
+        mConnection = new PersistentServiceConnection<>(
+                mContext,
+                mFakeExecutor,
+                mHandler,
+                mTransformer,
+                mIntent,
+                /* flags= */ Context.BIND_AUTO_CREATE,
+                CONNECTION_MIN_DURATION_MS,
+                MAX_RETRIES,
+                RETRY_DELAY_MS,
+                new TestInjector(mClock));
+
+        mClock.fastForward(1000);
+        mConnection.addCallback(mCallback);
+        when(mTransformer.convert(mBinder)).thenReturn(mResult);
+    }
+
+    @After
+    public void tearDown() {
+        mFakeExecutor.clearAll();
+    }
+
+    @Test
+    public void testConnect() {
+        mConnection.bind();
+        mConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        mFakeExecutor.runAll();
+        // Ensure that we did not schedule a retry
+        verify(mHandler, never()).postDelayed(any(), anyLong());
+    }
+
+    @Test
+    public void testRetryOnBindFailure() {
+        mConnection.bind();
+
+        verify(mContext, times(1)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+
+        // After disconnect, a reconnection should be attempted after the RETRY_DELAY_MS
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+        mFakeExecutor.runAll();
+        advanceTime(RETRY_DELAY_MS);
+        verify(mContext, times(2)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+
+        // Reconnect attempt #2
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+        mFakeExecutor.runAll();
+        advanceTime(RETRY_DELAY_MS * 2);
+        verify(mContext, times(3)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+
+        // There should be no more reconnect attempts, since the maximum is 2
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+        mFakeExecutor.runAll();
+        advanceTime(RETRY_DELAY_MS * 4);
+        verify(mContext, times(3)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+    }
+
+    @Test
+    public void testManualUnbindDoesNotReconnect() {
+        mConnection.bind();
+
+        verify(mContext, times(1)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+
+        mConnection.unbind();
+        // Ensure that disconnection after unbind does not reconnect.
+        mConnection.onServiceDisconnected(COMPONENT_NAME);
+        mFakeExecutor.runAll();
+        advanceTime(RETRY_DELAY_MS);
+
+        verify(mContext, times(1)).bindService(
+                eq(mIntent),
+                anyInt(),
+                eq(mFakeExecutor),
+                eq(mConnection));
+    }
+
+    private void advanceTime(long millis) {
+        mClock.fastForward(millis);
+        mHandler.timeAdvance();
+    }
+
+    static class TestInjector extends PersistentServiceConnection.Injector {
+        private final OffsettableClock mClock;
+
+        TestInjector(OffsettableClock clock) {
+            mClock = clock;
+        }
+
+        @Override
+        public long uptimeMillis() {
+            return mClock.now();
+        }
+    }
+
+    static class FakeExecutor implements Executor {
+        private final Queue<Runnable> mQueue = new ArrayDeque<>();
+
+        @Override
+        public void execute(Runnable command) {
+            mQueue.add(command);
+        }
+
+        public void runAll() {
+            while (!mQueue.isEmpty()) {
+                mQueue.remove().run();
+            }
+        }
+
+        public void clearAll() {
+            while (!mQueue.isEmpty()) {
+                mQueue.remove();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 0fb6ff8..b516e140 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -117,9 +117,7 @@
         if (mWindowLayoutChangeListeners.containsKey(context)
                 // In theory this method can be called on the same consumer with different context.
                 || mWindowLayoutChangeListeners.containsValue(consumer)) {
-            throw new IllegalArgumentException(
-                    "Context or Consumer has already been registered for WindowLayoutInfo"
-                            + " callback.");
+            return;
         }
         if (!context.isUiContext()) {
             throw new IllegalArgumentException("Context must be a UI Context, which should be"
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 82573b2..f615ad6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -45,6 +45,9 @@
         "src/com/android/wm/shell/util/**/*.java",
         "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
         "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+        "src/com/android/wm/shell/common/TransactionPool.java",
+        "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
     ],
     path: "src",
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 014f02b..8bba4404 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -15,38 +15,20 @@
  */
 package com.android.wm.shell.startingsurface;
 
-import static android.view.Choreographer.CALLBACK_COMMIT;
 import static android.view.View.GONE;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
 
 import android.animation.Animator;
-import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RadialGradient;
 import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.MathUtils;
 import android.util.Slog;
-import android.view.Choreographer;
 import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
 import android.window.SplashScreenView;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.TransactionPool;
 
 /**
@@ -55,14 +37,8 @@
  */
 public class SplashScreenExitAnimation implements Animator.AnimatorListener {
     private static final boolean DEBUG_EXIT_ANIMATION = false;
-    private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
     private static final String TAG = StartingWindowController.TAG;
 
-    private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
-    private static final Interpolator MASK_RADIUS_INTERPOLATOR =
-            new PathInterpolator(0f, 0f, 0.4f, 1f);
-    private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
-
     private final SurfaceControl mFirstWindowSurface;
     private final Rect mFirstWindowFrame = new Rect();
     private final SplashScreenView mSplashScreenView;
@@ -75,9 +51,6 @@
     private final float mBrandingStartAlpha;
     private final TransactionPool mTransactionPool;
 
-    private ValueAnimator mMainAnimator;
-    private ShiftUpAnimation mShiftUpAnimation;
-    private RadialVanishAnimation mRadialVanishAnimation;
     private Runnable mFinishCallback;
 
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
@@ -121,187 +94,10 @@
     }
 
     void startAnimations() {
-        mMainAnimator = createAnimator();
-        mMainAnimator.start();
-    }
-
-    // fade out icon, reveal app, shift up main window
-    private ValueAnimator createAnimator() {
-        // reveal app
-        final float transparentRatio = 0.8f;
-        final int globalHeight = mSplashScreenView.getHeight();
-        final int verticalCircleCenter = 0;
-        final int finalVerticalLength = globalHeight - verticalCircleCenter;
-        final int halfWidth = mSplashScreenView.getWidth() / 2;
-        final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
-                Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
-        final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
-        final float[] stops = {0f, transparentRatio, 1f};
-
-        mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
-        mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
-        mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
-        mRadialVanishAnimation.setRadialPaintParam(colors, stops);
-
-        if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
-            // shift up main window
-            View occludeHoleView = new View(mSplashScreenView.getContext());
-            if (DEBUG_EXIT_ANIMATION_BLEND) {
-                occludeHoleView.setBackgroundColor(Color.BLUE);
-            } else {
-                occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
-            }
-            final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
-                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
-            mSplashScreenView.addView(occludeHoleView, params);
-
-            mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
-        }
-
-        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
-        animator.setDuration(mAnimationDuration);
-        animator.setInterpolator(Interpolators.LINEAR);
-        animator.addListener(this);
-        animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
-        return animator;
-    }
-
-    private static class RadialVanishAnimation extends View {
-        private final SplashScreenView mView;
-        private int mInitRadius;
-        private int mFinishRadius;
-
-        private final Point mCircleCenter = new Point();
-        private final Matrix mVanishMatrix = new Matrix();
-        private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
-        RadialVanishAnimation(SplashScreenView target) {
-            super(target.getContext());
-            mView = target;
-            mView.addView(this);
-            mVanishPaint.setAlpha(0);
-        }
-
-        void onAnimationProgress(float linearProgress) {
-            if (mVanishPaint.getShader() == null) {
-                return;
-            }
-
-            final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
-            final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
-            final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
-
-            mVanishMatrix.setScale(scale, scale);
-            mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
-            mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
-            mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
-
-            postInvalidate();
-        }
-
-        void setRadius(int initRadius, int finishRadius) {
-            if (DEBUG_EXIT_ANIMATION) {
-                Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
-                        + " final " + finishRadius);
-            }
-            mInitRadius = initRadius;
-            mFinishRadius = finishRadius;
-        }
-
-        void setCircleCenter(int x, int y) {
-            if (DEBUG_EXIT_ANIMATION) {
-                Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
-            }
-            mCircleCenter.set(x, y);
-        }
-
-        void setRadialPaintParam(int[] colors, float[] stops) {
-            // setup gradient shader
-            final RadialGradient rShader =
-                    new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
-            mVanishPaint.setShader(rShader);
-            if (!DEBUG_EXIT_ANIMATION_BLEND) {
-                // We blend the reveal gradient with the splash screen using DST_OUT so that the
-                // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
-                // fully invisible when radius = finishRadius AND gradient opacity is 1.
-                mVanishPaint.setBlendMode(BlendMode.DST_OUT);
-            }
-        }
-
-        @Override
-        protected void onDraw(Canvas canvas) {
-            super.onDraw(canvas);
-            canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
-        }
-    }
-
-    private final class ShiftUpAnimation {
-        private final float mFromYDelta;
-        private final float mToYDelta;
-        private final View mOccludeHoleView;
-        private final SyncRtSurfaceTransactionApplier mApplier;
-        private final Matrix mTmpTransform = new Matrix();
-
-        ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
-            mFromYDelta = fromYDelta;
-            mToYDelta = toYDelta;
-            mOccludeHoleView = occludeHoleView;
-            mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
-        }
-
-        void onAnimationProgress(float linearProgress) {
-            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
-                    || !mSplashScreenView.isAttachedToWindow()) {
-                return;
-            }
-
-            final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
-            final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
-
-            mOccludeHoleView.setTranslationY(dy);
-            mTmpTransform.setTranslate(0 /* dx */, dy);
-
-            // set the vsyncId to ensure the transaction doesn't get applied too early.
-            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
-            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-            mTmpTransform.postTranslate(mFirstWindowFrame.left,
-                    mFirstWindowFrame.top + mMainWindowShiftLength);
-
-            SyncRtSurfaceTransactionApplier.SurfaceParams
-                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
-                    .Builder(mFirstWindowSurface)
-                    .withMatrix(mTmpTransform)
-                    .withMergeTransaction(tx)
-                    .build();
-            mApplier.scheduleApply(params);
-
-            mTransactionPool.release(tx);
-        }
-
-        void finish() {
-            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
-                return;
-            }
-            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
-            if (mSplashScreenView.isAttachedToWindow()) {
-                tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-
-                SyncRtSurfaceTransactionApplier.SurfaceParams
-                        params = new SyncRtSurfaceTransactionApplier.SurfaceParams
-                        .Builder(mFirstWindowSurface)
-                        .withWindowCrop(null)
-                        .withMergeTransaction(tx)
-                        .build();
-                mApplier.scheduleApply(params);
-            } else {
-                tx.setWindowCrop(mFirstWindowSurface, null);
-                tx.apply();
-            }
-            mTransactionPool.release(tx);
-
-            Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
-                    mFirstWindowSurface::release, null);
-        }
+        SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+                mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+                mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+                mAppRevealDuration, this);
     }
 
     private void reset() {
@@ -316,9 +112,6 @@
                 mFinishCallback = null;
             }
         }
-        if (mShiftUpAnimation != null) {
-            mShiftUpAnimation.finish();
-        }
     }
 
     @Override
@@ -342,40 +135,4 @@
     public void onAnimationRepeat(Animator animation) {
         // ignore
     }
-
-    private void onFadeOutProgress(float linearProgress) {
-        final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
-                getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
-        final View iconView = mSplashScreenView.getIconView();
-        final View brandingView = mSplashScreenView.getBrandingView();
-        if (iconView != null) {
-            iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
-        }
-        if (brandingView != null) {
-            brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
-        }
-    }
-
-    private void onAnimationProgress(float linearProgress) {
-        onFadeOutProgress(linearProgress);
-
-        final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
-                mAppRevealDuration);
-
-        if (mRadialVanishAnimation != null) {
-            mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
-        }
-
-        if (mShiftUpAnimation != null) {
-            mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
-        }
-    }
-
-    private float getProgress(float linearProgress, long delay, long duration) {
-        return MathUtils.constrain(
-                (linearProgress * (mAnimationDuration) - delay) / duration,
-                0.0f,
-                1.0f
-        );
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
new file mode 100644
index 0000000..3098e55
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.startingsurface;
+
+import static android.view.Choreographer.CALLBACK_COMMIT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.SplashScreenView;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.TransactionPool;
+
+/**
+ * Utilities for creating the splash screen window animations.
+ * @hide
+ */
+public class SplashScreenExitAnimationUtils {
+    private static final boolean DEBUG_EXIT_ANIMATION = false;
+    private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+    private static final String TAG = "SplashScreenExitAnimationUtils";
+
+    private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
+    private static final Interpolator MASK_RADIUS_INTERPOLATOR =
+            new PathInterpolator(0f, 0f, 0.4f, 1f);
+    private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
+
+    /**
+     * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+     * window.
+     * @hide
+     */
+    public static void startAnimations(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+        ValueAnimator animator =
+                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                        animatorListener);
+        animator.start();
+    }
+
+    /**
+     * Creates the animator to fade out the icon, reveal the app, and shift up main window.
+     * @hide
+     */
+    private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+        // reveal app
+        final float transparentRatio = 0.8f;
+        final int globalHeight = splashScreenView.getHeight();
+        final int verticalCircleCenter = 0;
+        final int finalVerticalLength = globalHeight - verticalCircleCenter;
+        final int halfWidth = splashScreenView.getWidth() / 2;
+        final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
+                Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
+        final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
+        final float[] stops = {0f, transparentRatio, 1f};
+
+        RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
+        radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
+        radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
+        radialVanishAnimation.setRadialPaintParam(colors, stops);
+
+        View occludeHoleView = null;
+        ShiftUpAnimation shiftUpAnimation = null;
+        if (firstWindowSurface != null && firstWindowSurface.isValid()) {
+            // shift up main window
+            occludeHoleView = new View(splashScreenView.getContext());
+            if (DEBUG_EXIT_ANIMATION_BLEND) {
+                occludeHoleView.setBackgroundColor(Color.BLUE);
+            } else if (splashScreenView instanceof SplashScreenView) {
+                occludeHoleView.setBackgroundColor(
+                        ((SplashScreenView) splashScreenView).getInitBackgroundColor());
+            } else {
+                occludeHoleView.setBackgroundColor(
+                        isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
+            }
+            final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
+            splashScreenView.addView(occludeHoleView, params);
+
+            shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
+                    firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
+                    mMainWindowShiftLength);
+        }
+
+        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setDuration(animationDuration);
+        animator.setInterpolator(Interpolators.LINEAR);
+        if (animatorListener != null) {
+            animator.addListener(animatorListener);
+        }
+        View finalOccludeHoleView = occludeHoleView;
+        ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (finalShiftUpAnimation != null) {
+                    finalShiftUpAnimation.finish();
+                }
+                splashScreenView.removeView(radialVanishAnimation);
+                splashScreenView.removeView(finalOccludeHoleView);
+            }
+        });
+        animator.addUpdateListener(animation -> {
+            float linearProgress = (float) animation.getAnimatedValue();
+
+            // Fade out progress
+            final float iconProgress =
+                    ICON_INTERPOLATOR.getInterpolation(getProgress(
+                            linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+            View iconView = null;
+            View brandingView = null;
+            if (splashScreenView instanceof SplashScreenView) {
+                iconView = ((SplashScreenView) splashScreenView).getIconView();
+                brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+            }
+            if (iconView != null) {
+                iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
+            }
+            if (brandingView != null) {
+                brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
+            }
+
+            final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
+                    appRevealDuration, animationDuration);
+
+            radialVanishAnimation.onAnimationProgress(revealLinearProgress);
+
+            if (finalShiftUpAnimation != null) {
+                finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
+            }
+        });
+        return animator;
+    }
+
+    private static float getProgress(float linearProgress, long delay, long duration,
+                                     int animationDuration) {
+        return MathUtils.constrain(
+                (linearProgress * (animationDuration) - delay) / duration,
+                0.0f,
+                1.0f
+        );
+    }
+
+    private static boolean isDarkTheme(Context context) {
+        Configuration configuration = context.getResources().getConfiguration();
+        int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        return nightMode == Configuration.UI_MODE_NIGHT_YES;
+    }
+
+    /**
+     * View which creates a circular reveal of the underlying view.
+     * @hide
+     */
+    @SuppressLint("ViewConstructor")
+    public static class RadialVanishAnimation extends View {
+        private final ViewGroup mView;
+        private int mInitRadius;
+        private int mFinishRadius;
+
+        private final Point mCircleCenter = new Point();
+        private final Matrix mVanishMatrix = new Matrix();
+        private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+        public RadialVanishAnimation(ViewGroup target) {
+            super(target.getContext());
+            mView = target;
+            mView.addView(this);
+            if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+                ((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
+            }
+            mVanishPaint.setAlpha(0);
+        }
+
+        void onAnimationProgress(float linearProgress) {
+            if (mVanishPaint.getShader() == null) {
+                return;
+            }
+
+            final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
+            final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
+            final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
+
+            mVanishMatrix.setScale(scale, scale);
+            mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
+            mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
+            mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
+
+            postInvalidate();
+        }
+
+        void setRadius(int initRadius, int finishRadius) {
+            if (DEBUG_EXIT_ANIMATION) {
+                Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
+                        + " final " + finishRadius);
+            }
+            mInitRadius = initRadius;
+            mFinishRadius = finishRadius;
+        }
+
+        void setCircleCenter(int x, int y) {
+            if (DEBUG_EXIT_ANIMATION) {
+                Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
+            }
+            mCircleCenter.set(x, y);
+        }
+
+        void setRadialPaintParam(int[] colors, float[] stops) {
+            // setup gradient shader
+            final RadialGradient rShader =
+                    new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
+            mVanishPaint.setShader(rShader);
+            if (!DEBUG_EXIT_ANIMATION_BLEND) {
+                // We blend the reveal gradient with the splash screen using DST_OUT so that the
+                // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
+                // fully invisible when radius = finishRadius AND gradient opacity is 1.
+                mVanishPaint.setBlendMode(BlendMode.DST_OUT);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
+        }
+    }
+
+    /**
+     * Shifts up the main window.
+     * @hide
+     */
+    public static final class ShiftUpAnimation {
+        private final float mFromYDelta;
+        private final float mToYDelta;
+        private final View mOccludeHoleView;
+        private final SyncRtSurfaceTransactionApplier mApplier;
+        private final Matrix mTmpTransform = new Matrix();
+        private final SurfaceControl mFirstWindowSurface;
+        private final ViewGroup mSplashScreenView;
+        private final TransactionPool mTransactionPool;
+        private final Rect mFirstWindowFrame;
+        private final int mMainWindowShiftLength;
+
+        public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
+                                SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
+                                TransactionPool transactionPool, Rect firstWindowFrame,
+                                int mainWindowShiftLength) {
+            mFromYDelta = fromYDelta;
+            mToYDelta = toYDelta;
+            mOccludeHoleView = occludeHoleView;
+            mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
+            mFirstWindowSurface = firstWindowSurface;
+            mSplashScreenView = splashScreenView;
+            mTransactionPool = transactionPool;
+            mFirstWindowFrame = firstWindowFrame;
+            mMainWindowShiftLength = mainWindowShiftLength;
+        }
+
+        void onAnimationProgress(float linearProgress) {
+            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
+                    || !mSplashScreenView.isAttachedToWindow()) {
+                return;
+            }
+
+            final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
+            final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
+
+            mOccludeHoleView.setTranslationY(dy);
+            mTmpTransform.setTranslate(0 /* dx */, dy);
+
+            // set the vsyncId to ensure the transaction doesn't get applied too early.
+            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+            mTmpTransform.postTranslate(mFirstWindowFrame.left,
+                    mFirstWindowFrame.top + mMainWindowShiftLength);
+
+            SyncRtSurfaceTransactionApplier.SurfaceParams
+                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+                    .Builder(mFirstWindowSurface)
+                    .withMatrix(mTmpTransform)
+                    .withMergeTransaction(tx)
+                    .build();
+            mApplier.scheduleApply(params);
+
+            mTransactionPool.release(tx);
+        }
+
+        void finish() {
+            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
+                return;
+            }
+            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+            if (mSplashScreenView.isAttachedToWindow()) {
+                tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+
+                SyncRtSurfaceTransactionApplier.SurfaceParams
+                        params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+                        .Builder(mFirstWindowSurface)
+                        .withWindowCrop(null)
+                        .withMergeTransaction(tx)
+                        .build();
+                mApplier.scheduleApply(params);
+            } else {
+                tx.setWindowCrop(mFirstWindowSurface, null);
+                tx.apply();
+            }
+            mTransactionPool.release(tx);
+
+            Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
+                    mFirstWindowSurface::release, null);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 3d8525b..1dc03b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,12 +18,12 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.Test
 
 /** Base class for pip expand tests */
 abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    protected val testApp = FixedOrientationAppHelper(instrumentation)
+    protected val testApp = SimpleAppHelper(instrumentation)
 
     /**
      * Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623..46fbe7c 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1020,6 +1020,40 @@
     return base::unexpected(std::nullopt);
 }
 
+template <typename TChar, typename SP>
+base::expected<size_t, NullOrIOError> ResStringPool::stringIndex(
+        SP sp, std::unordered_map<SP, size_t>& map) const
+{
+    AutoMutex lock(mStringIndexLock);
+
+    if (map.empty()) {
+        // build string index on the first call
+        for (size_t i = 0; i < mHeader->stringCount; i++) {
+            base::expected<SP, NullOrIOError> s;
+            if constexpr(std::is_same_v<TChar, char16_t>) {
+                s = stringAt(i);
+            } else {
+                s = string8At(i);
+            }
+            if (s.has_value()) {
+                const auto r = map.insert({*s, i});
+                if (!r.second) {
+                    ALOGE("failed to build string index, string id=%zu\n", i);
+                }
+            } else {
+                return base::unexpected(s.error());
+            }
+        }
+    }
+
+    if (!map.empty()) {
+        const auto result = map.find(sp);
+        if (result != map.end())
+            return result->second;
+    }
+    return base::unexpected(std::nullopt);
+}
+
 base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_t* str,
                                                                    size_t strLen) const
 {
@@ -1027,134 +1061,28 @@
         return base::unexpected(std::nullopt);
     }
 
-    if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
-        if (kDebugStringPoolNoisy) {
-            ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string());
-        }
+    if (kDebugStringPoolNoisy) {
+        ALOGI("indexOfString (%s): %s", isUTF8() ? "UTF-8" : "UTF-16",
+                String8(str, strLen).string());
+    }
 
-        // The string pool contains UTF 8 strings; we don't want to cause
-        // temporary UTF-16 strings to be created as we search.
-        if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
-            // Do a binary search for the string...  this is a little tricky,
-            // because the strings are sorted with strzcmp16().  So to match
-            // the ordering, we need to convert strings in the pool to UTF-16.
-            // But we don't want to hit the cache, so instead we will have a
-            // local temporary allocation for the conversions.
-            size_t convBufferLen = strLen + 4;
-            std::vector<char16_t> convBuffer(convBufferLen);
-            ssize_t l = 0;
-            ssize_t h = mHeader->stringCount-1;
-
-            ssize_t mid;
-            while (l <= h) {
-                mid = l + (h - l)/2;
-                int c = -1;
-                const base::expected<StringPiece, NullOrIOError> s = string8At(mid);
-                if (UNLIKELY(IsIOError(s))) {
-                    return base::unexpected(s.error());
-                }
-                if (s.has_value()) {
-                    char16_t* end = utf8_to_utf16(reinterpret_cast<const uint8_t*>(s->data()),
-                                                  s->size(), convBuffer.data(), convBufferLen);
-                    c = strzcmp16(convBuffer.data(), end-convBuffer.data(), str, strLen);
-                }
-                if (kDebugStringPoolNoisy) {
-                    ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
-                          s->data(), c, (int)l, (int)mid, (int)h);
-                }
-                if (c == 0) {
-                    if (kDebugStringPoolNoisy) {
-                        ALOGI("MATCH!");
-                    }
-                    return mid;
-                } else if (c < 0) {
-                    l = mid + 1;
-                } else {
-                    h = mid - 1;
-                }
-            }
-        } else {
-            // It is unusual to get the ID from an unsorted string block...
-            // most often this happens because we want to get IDs for style
-            // span tags; since those always appear at the end of the string
-            // block, start searching at the back.
-            String8 str8(str, strLen);
-            const size_t str8Len = str8.size();
-            for (int i=mHeader->stringCount-1; i>=0; i--) {
-                const base::expected<StringPiece, NullOrIOError> s = string8At(i);
-                if (UNLIKELY(IsIOError(s))) {
-                    return base::unexpected(s.error());
-                }
-                if (s.has_value()) {
-                    if (kDebugStringPoolNoisy) {
-                        ALOGI("Looking at %s, i=%d\n", s->data(), i);
-                    }
-                    if (str8Len == s->size()
-                            && memcmp(s->data(), str8.string(), str8Len) == 0) {
-                        if (kDebugStringPoolNoisy) {
-                            ALOGI("MATCH!");
-                        }
-                        return i;
-                    }
-                }
-            }
-        }
-
+    base::expected<size_t, NullOrIOError> idx;
+    if (isUTF8()) {
+        auto str8 = String8(str, strLen);
+        idx = stringIndex<char>(StringPiece(str8.c_str(), str8.size()), mStringIndex8);
     } else {
+        idx = stringIndex<char16_t>(StringPiece16(str, strLen), mStringIndex16);
+    }
+
+    if (UNLIKELY(!idx.has_value())) {
+        return base::unexpected(idx.error());
+    }
+
+    if (*idx < mHeader->stringCount) {
         if (kDebugStringPoolNoisy) {
-            ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string());
+            ALOGI("MATCH! (idx=%zu)", *idx);
         }
-
-        if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
-            // Do a binary search for the string...
-            ssize_t l = 0;
-            ssize_t h = mHeader->stringCount-1;
-
-            ssize_t mid;
-            while (l <= h) {
-                mid = l + (h - l)/2;
-                const base::expected<StringPiece16, NullOrIOError> s = stringAt(mid);
-                if (UNLIKELY(IsIOError(s))) {
-                    return base::unexpected(s.error());
-                }
-                int c = s.has_value() ? strzcmp16(s->data(), s->size(), str, strLen) : -1;
-                if (kDebugStringPoolNoisy) {
-                    ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
-                          String8(s->data(), s->size()).string(), c, (int)l, (int)mid, (int)h);
-                }
-                if (c == 0) {
-                    if (kDebugStringPoolNoisy) {
-                        ALOGI("MATCH!");
-                    }
-                    return mid;
-                } else if (c < 0) {
-                    l = mid + 1;
-                } else {
-                    h = mid - 1;
-                }
-            }
-        } else {
-            // It is unusual to get the ID from an unsorted string block...
-            // most often this happens because we want to get IDs for style
-            // span tags; since those always appear at the end of the string
-            // block, start searching at the back.
-            for (int i=mHeader->stringCount-1; i>=0; i--) {
-                const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
-                if (UNLIKELY(IsIOError(s))) {
-                    return base::unexpected(s.error());
-                }
-                if (kDebugStringPoolNoisy) {
-                    ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).string(), i);
-                }
-                if (s.has_value() && strLen == s->size() &&
-                        strzcmp16(s->data(), s->size(), str, strLen) == 0) {
-                    if (kDebugStringPoolNoisy) {
-                        ALOGI("MATCH!");
-                    }
-                    return i;
-                }
-            }
-        }
+        return *idx;
     }
     return base::unexpected(std::nullopt);
 }
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..24628cd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -41,6 +41,7 @@
 #include <array>
 #include <map>
 #include <memory>
+#include <unordered_map>
 
 namespace android {
 
@@ -562,8 +563,17 @@
     incfs::map_ptr<uint32_t>                      mStyles;
     uint32_t                                      mStylePoolSize;    // number of uint32_t
 
+    // mStringIndex is used to quickly map a string to its ID
+    mutable Mutex                                       mStringIndexLock;
+    mutable std::unordered_map<StringPiece, size_t>     mStringIndex8;
+    mutable std::unordered_map<StringPiece16, size_t>   mStringIndex16;
+
     base::expected<StringPiece, NullOrIOError> stringDecodeAt(
         size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
+
+    template <typename TChar, typename SP=BasicStringPiece<TChar>>
+    base::expected<size_t, NullOrIOError> stringIndex(
+        SP str, std::unordered_map<SP, size_t>& map) const;
 };
 
 /**
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 50aa6fe..6ae7dfb 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -44,6 +44,7 @@
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
     void onRequestCurrentTvInputId(int seq);
+    void onRequestStartRecording(in Uri programUri, int seq);
     void onRequestSigning(
             in String id, in String algorithm, in String alias, in byte[] data, int seq);
     void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index c8c695f..84b9c9e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -63,6 +63,7 @@
     void notifyContentAllowed(in IBinder sessionToken, int userId);
     void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
+    void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 818c287..95b4ffa 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -53,6 +53,7 @@
     void notifyContentAllowed();
     void notifyContentBlocked(in String rating);
     void notifySignalStrength(int strength);
+    void notifyRecordingStarted(in String recordingId);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 32b08b7..6478057 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -43,6 +43,7 @@
     void onRequestStreamVolume();
     void onRequestTrackInfoList();
     void onRequestCurrentTvInputId();
+    void onRequestStartRecording(in Uri programUri);
     void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index a72f34c..042cb15 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -81,6 +81,7 @@
     private static final int DO_CREATE_MEDIA_VIEW = 27;
     private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
     private static final int DO_REMOVE_MEDIA_VIEW = 29;
+    private static final int DO_NOTIFY_RECORDING_STARTED = 30;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -164,6 +165,10 @@
                 mSessionImpl.sendCurrentTvInputId((String) msg.obj);
                 break;
             }
+            case DO_NOTIFY_RECORDING_STARTED: {
+                mSessionImpl.notifyRecordingStarted((String) msg.obj);
+                break;
+            }
             case DO_SEND_SIGNING_RESULT: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -381,6 +386,12 @@
     }
 
     @Override
+    public void notifyRecordingStarted(String recordingId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+                DO_NOTIFY_RECORDING_STARTED, recordingId));
+    }
+
+    @Override
     public void setSurface(Surface surface) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index c0261f2..a27fd10 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -487,6 +487,18 @@
             }
 
             @Override
+            public void onRequestStartRecording(Uri programUri, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestStartRecording(programUri);
+                }
+            }
+
+            @Override
             public void onRequestSigning(
                     String id, String algorithm, String alias, byte[] data, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -1035,6 +1047,18 @@
             }
         }
 
+        void notifyRecordingStarted(@Nullable String recordingId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingStarted(mToken, recordingId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1696,6 +1720,15 @@
             });
         }
 
+        void postRequestStartRecording(Uri programUri) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestStartRecording(mSession, programUri);
+                }
+            });
+        }
+
         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1847,6 +1880,15 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#RequestStartRecording} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         */
+        public void onRequestStartRecording(Session session, Uri programUri) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index f5b4322..3d65effa 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -455,6 +455,13 @@
         }
 
         /**
+         * Receives started recording's ID.
+         * @hide
+         */
+        public void onRecordingStarted(@Nullable String recordingId) {
+        }
+
+        /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
          *        {@link Session#requestSigning(String, String, String, byte[])}
@@ -905,6 +912,27 @@
         }
 
         /**
+         * Requests starting of recording
+         *
+         * @hide
+         */
+        @CallSuper
+        public void requestStartRecording(@Nullable Uri programUri) {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestStartRecording");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestStartRecording(programUri);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestStartRecording", e);
+                }
+            });
+        }
+
+        /**
          * Requests signing of the given data.
          *
          * <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -1114,6 +1142,10 @@
             onAdResponse(response);
         }
 
+        void notifyRecordingStarted(String recordingId) {
+            onRecordingStarted(recordingId);
+        }
+
         /**
          * Notifies when the session state is changed.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 53b0324..76ba69c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,6 +581,22 @@
     }
 
     /**
+     * Alerts the TV interactive app that a recording has been started with recordingId
+     *
+     * @param recordingId The Id of the recording started
+     *
+     * @hide
+     */
+    public void notifyRecordingStarted(@Nullable String recordingId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingStarted");
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingStarted(recordingId);
+        }
+    }
+
+    /**
      * Sends signing result to related TV interactive app.
      *
      * <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -841,6 +857,19 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
+         * is called.
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param programUri The program URI to record
+         *
+         * @hide
+         */
+        public void onRequestStartRecording(
+                @NonNull String iAppServiceId,
+                @Nullable Uri programUri) {
+        }
+
+        /**
          * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
@@ -1164,6 +1193,20 @@
         }
 
         @Override
+        public void onRequestStartRecording(Session session, Uri programUri) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestStartRecording");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestStartRecording - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestStartRecording(mIAppServiceId, programUri);
+            }
+        }
+
+        @Override
         public void onRequestSigning(
                 Session session, String id, String algorithm, String alias, byte[] data) {
             if (DEBUG) {
diff --git a/media/java/android/media/tv/tuner/TunerVersionChecker.java b/media/java/android/media/tv/tuner/TunerVersionChecker.java
index 3e13bed..f29a93c 100644
--- a/media/java/android/media/tv/tuner/TunerVersionChecker.java
+++ b/media/java/android/media/tv/tuner/TunerVersionChecker.java
@@ -59,6 +59,10 @@
      * Tuner version 2.0.
      */
     public static final int TUNER_VERSION_2_0 = (2 << 16);
+    /**
+     * Tuner version 3.0.
+     */
+    public static final int TUNER_VERSION_3_0 = (3 << 16);
 
     /**
      * Get the current running Tuner version.
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index 11e6999..0680240 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -23,6 +23,7 @@
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
 import android.media.tv.tuner.filter.Filter;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -91,6 +92,7 @@
     private native int nativeAttachFilter(Filter filter);
     private native int nativeDetachFilter(Filter filter);
     private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeSetStatusCheckIntervalHint(long durationInMs);
     private native int nativeStartDvr();
     private native int nativeStopDvr();
     private native int nativeFlushDvr();
@@ -177,6 +179,35 @@
     }
 
     /**
+     * Set playback buffer status check time interval.
+     *
+     * This status check time interval will be used by the Dvr to decide how often to evaluate
+     * data. The default value will be decided by HAL if it’s not set.
+     *
+     * <p>This functionality is only available in Tuner version 3.0 and higher and will otherwise
+     * return a {@link Tuner#RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()}
+     * to get the version information.
+     *
+     * @param durationInMs specifies the duration of the delay in milliseconds.
+     *
+     * @return one of the following results:
+     * {@link Tuner#RESULT_SUCCESS} if succeed,
+     * {@link Tuner#RESULT_UNAVAILABLE} if Dvr is unavailable or unsupported HAL versions,
+     * {@link Tuner#RESULT_NOT_INITIALIZED} if Dvr is not initialized,
+     * {@link Tuner#RESULT_INVALID_STATE} if Dvr is in a wrong state,
+     * {@link Tuner#RESULT_INVALID_ARGUMENT}  if the input parameter is invalid.
+     */
+    @Result
+    public int setPlaybackBufferStatusCheckIntervalHint(long durationInMs) {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_3_0, "Set status check interval hint")) {
+            // no-op
+            return Tuner.RESULT_UNAVAILABLE;
+        }
+        return nativeSetStatusCheckIntervalHint(durationInMs);
+    }
+
+    /**
      * Starts DVR.
      *
      * <p>Starts consuming playback data or producing data for recording.
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index e72026a..1a65832 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -22,6 +22,7 @@
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.Tuner.Result;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
 import android.media.tv.tuner.filter.Filter;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -53,6 +54,7 @@
     private native int nativeAttachFilter(Filter filter);
     private native int nativeDetachFilter(Filter filter);
     private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeSetStatusCheckIntervalHint(long durationInMs);
     private native int nativeStartDvr();
     private native int nativeStopDvr();
     private native int nativeFlushDvr();
@@ -131,6 +133,35 @@
     }
 
     /**
+     * Set record buffer status check time interval.
+     *
+     * This status check time interval will be used by the Dvr to decide how often to evaluate
+     * data. The default value will be decided by HAL if it’s not set.
+     *
+     * <p>This functionality is only available in Tuner version 3.0 and higher and will otherwise
+     * return a {@link Tuner#RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()}
+     * to get the version information.
+     *
+     * @param durationInMs specifies the duration of the delay in milliseconds.
+     *
+     * @return one of the following results:
+     * {@link Tuner#RESULT_SUCCESS} if succeed,
+     * {@link Tuner#RESULT_UNAVAILABLE} if Dvr is unavailable or unsupported HAL versions,
+     * {@link Tuner#RESULT_NOT_INITIALIZED} if Dvr is not initialized,
+     * {@link Tuner#RESULT_INVALID_STATE} if Dvr is in a wrong state,
+     * {@link Tuner#RESULT_INVALID_ARGUMENT}  if the input parameter is invalid.
+     */
+    @Result
+    public int setRecordBufferStatusCheckIntervalHint(long durationInMs) {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_3_0, "Set status check interval hint")) {
+            // no-op
+            return Tuner.RESULT_UNAVAILABLE;
+        }
+        return nativeSetStatusCheckIntervalHint(durationInMs);
+    }
+
+    /**
      * Starts DVR.
      *
      * <p>Starts consuming playback data or producing data for recording.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 96a3781..a0304bb 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -175,7 +175,7 @@
 
     shared_libs: [
         "android.hardware.graphics.bufferqueue@2.0",
-        "android.hardware.tv.tuner-V1-ndk",
+        "android.hardware.tv.tuner-V2-ndk",
         "libbinder_ndk",
         "libandroid_runtime",
         "libcutils",
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 244730b..c18edcd 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -4485,6 +4485,17 @@
     return (jint)result;
 }
 
+static jint android_media_tv_Tuner_set_status_check_interval_hint(JNIEnv *env, jobject dvr,
+                                                                  jlong durationInMs) {
+    sp<DvrClient> dvrClient = getDvrClient(env, dvr);
+    if (dvrClient == nullptr) {
+        ALOGD("Failed to set status check interval hint: dvr client not found");
+        return (int)Result::NOT_INITIALIZED;
+    }
+    Result result = dvrClient->setStatusCheckIntervalHint(durationInMs);
+    return (jint)result;
+}
+
 static jint android_media_tv_Tuner_start_dvr(JNIEnv *env, jobject dvr) {
     sp<DvrClient> dvrClient = getDvrClient(env, dvr);
     if (dvrClient == nullptr) {
@@ -4828,6 +4839,8 @@
             (void *)android_media_tv_Tuner_detach_filter },
     { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
             (void *)android_media_tv_Tuner_configure_dvr },
+    { "nativeSetStatusCheckIntervalHint", "(J)I",
+            (void *)android_media_tv_Tuner_set_status_check_interval_hint},
     { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
     { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
     { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
@@ -4844,6 +4857,8 @@
             (void *)android_media_tv_Tuner_detach_filter},
     { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
             (void *)android_media_tv_Tuner_configure_dvr},
+    { "nativeSetStatusCheckIntervalHint", "(J)I",
+            (void *)android_media_tv_Tuner_set_status_check_interval_hint},
     { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr},
     { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr},
     { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr},
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 05683b6..6e47052 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -303,7 +303,17 @@
 
     return Result::INVALID_STATE;
 }
+Result DvrClient::setStatusCheckIntervalHint(int64_t durationInMs) {
+    if (mTunerDvr == nullptr) {
+      return Result::INVALID_STATE;
+    }
+    if (durationInMs < 0) {
+      return Result::INVALID_ARGUMENT;
+    }
 
+    Status s = mTunerDvr->setStatusCheckIntervalHint(durationInMs);
+    return ClientHelper::getServiceSpecificErrorCode(s);
+}
 /////////////// TunerDvrCallback ///////////////////////
 TunerDvrCallback::TunerDvrCallback(sp<DvrClientCallback> dvrClientCallback)
         : mDvrClientCallback(dvrClientCallback) {}
diff --git a/media/jni/tuner/DvrClient.h b/media/jni/tuner/DvrClient.h
index 61c0325..40ed75b 100644
--- a/media/jni/tuner/DvrClient.h
+++ b/media/jni/tuner/DvrClient.h
@@ -126,6 +126,11 @@
      */
     Result close();
 
+    /**
+     * Set status check time interval.
+     */
+    Result setStatusCheckIntervalHint(int64_t durationInMs);
+
 private:
     /**
      * An AIDL Tuner Dvr Singleton assigned at the first time the Tuner Client
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 21d4d80..8913799 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -139,8 +139,18 @@
 }
 
 ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
-    return reinterpret_cast<ASurfaceControl*>(
-            android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj));
+    LOG_ALWAYS_FATAL_IF(!env,
+                        "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+    LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
+                        "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
+                        "argument");
+    SurfaceControl* surfaceControl =
+            android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
+    LOG_ALWAYS_FATAL_IF(!surfaceControl,
+                        "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
+                        "valid");
+    SurfaceControl_acquire(surfaceControl);
+    return reinterpret_cast<ASurfaceControl*>(surfaceControl);
 }
 
 struct ASurfaceControlStats {
@@ -200,8 +210,17 @@
 }
 
 ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
-    return reinterpret_cast<ASurfaceTransaction*>(
-            android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj));
+    LOG_ALWAYS_FATAL_IF(!env,
+                        "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+    LOG_ALWAYS_FATAL_IF(!transactionObj,
+                        "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+                        "argument");
+    Transaction* transaction =
+            android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
+    LOG_ALWAYS_FATAL_IF(!transaction,
+                        "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
+                        "valid");
+    return reinterpret_cast<ASurfaceTransaction*>(transaction);
 }
 
 void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 9818ee7..56715b4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -46,6 +46,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -331,6 +332,7 @@
     private void onUserSelectedDevice(@NonNull DeviceFilterPair<?> selectedDevice) {
         final MacAddress macAddress = selectedDevice.getMacAddress();
         mRequest.setDisplayName(selectedDevice.getDisplayName());
+        mRequest.setAssociatedDevice(new AssociatedDevice(selectedDevice.getDevice()));
         onAssociationApproved(macAddress);
     }
 
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 2901705..2c24bf1 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -3,6 +3,7 @@
   <string name="string_cancel">Cancel</string>
   <string name="string_continue">Continue</string>
   <string name="string_more_options">More options</string>
+  <string name="string_create_at_another_place">Create at another place</string>
   <string name="string_no_thanks">No thanks</string>
   <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
   <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
@@ -10,4 +11,5 @@
   <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
   <string name="choose_create_option_title">Create a passkey at</string>
   <string name="choose_sign_in_title">Use saved sign in</string>
+  <string name="create_passkey_at">Create passkey at</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 5918633..46bf19c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -1,6 +1,14 @@
 package com.android.credentialmanager
 
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
 import android.content.Context
+import android.content.Intent
+import android.credentials.ui.Entry
+import android.credentials.ui.ProviderData
+import android.credentials.ui.RequestInfo
+import android.graphics.drawable.Icon
+import android.os.Binder
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.createflow.CreatePasskeyUiState
 import com.android.credentialmanager.createflow.CreateScreenState
@@ -11,8 +19,82 @@
 
 // Consider repo per screen, similar to view model?
 class CredentialManagerRepo(
-  private val context: Context
+  private val context: Context,
+  intent: Intent,
 ) {
+  private val requestInfo: RequestInfo
+  private val providerList: List<ProviderData>
+
+  init {
+    requestInfo = intent.extras?.getParcelable(
+      RequestInfo.EXTRA_REQUEST_INFO,
+      RequestInfo::class.java
+    ) ?: RequestInfo(
+      Binder(),
+      RequestInfo.TYPE_CREATE,
+      /*isFirstUsage=*/false
+    )
+
+    providerList = intent.extras?.getParcelableArrayList(
+      ProviderData.EXTRA_PROVIDER_DATA_LIST,
+      ProviderData::class.java
+    ) ?: testProviderList()
+  }
+
+  private fun testProviderList(): List<ProviderData> {
+    return listOf(
+      ProviderData(
+        "com.google",
+        listOf<Entry>(
+          newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett"),
+          newEntry(2, "elisa.work@google.com", "Elisa Backett Work"),
+        ),
+        listOf<Entry>(
+          newEntry(3, "Go to Settings", ""),
+          newEntry(4, "Switch Account", ""),
+        ),
+        null
+      ),
+      ProviderData(
+        "com.dashlane",
+        listOf<Entry>(
+          newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett"),
+          newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work"),
+        ),
+        listOf<Entry>(
+          newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app"),
+        ),
+        null
+      ),
+      ProviderData(
+        "com.lastpass",
+        listOf<Entry>(
+          newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett"),
+        ),
+        listOf<Entry>(),
+        null
+      )
+
+    )
+  }
+
+  private fun newEntry(id: Int, title: String, subtitle: String): Entry {
+    val slice = Slice.Builder(
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
+    )
+      .addText(title, null, listOf(Entry.HINT_TITLE))
+      .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+      .addIcon(
+        Icon.createWithResource(context, R.drawable.ic_passkey),
+        null,
+        listOf(Entry.HINT_ICON))
+      .build()
+    return Entry(
+      id,
+      slice
+    )
+  }
+
   private fun getCredentialProviderList():
     List<com.android.credentialmanager.getflow.ProviderInfo> {
       return listOf(
@@ -140,8 +222,11 @@
   companion object {
     lateinit var repo: CredentialManagerRepo
 
-    fun setup(context: Context) {
-      repo = CredentialManagerRepo(context)
+    fun setup(
+      context: Context,
+      intent: Intent,
+    ) {
+      repo = CredentialManagerRepo(context, intent)
     }
 
     fun getInstance(): CredentialManagerRepo {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5cd6a13..98c824c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -15,7 +15,7 @@
 class CredentialSelectorActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
-    CredentialManagerRepo.setup(this)
+    CredentialManagerRepo.setup(this, intent)
     val startDestination = intent.extras?.getString(
       "start_destination",
       "CREATE_PASSKEY"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 5aa1e9b..044688b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,4 +22,5 @@
   PASSKEY_INTRO,
   PROVIDER_SELECTION,
   CREATION_OPTION_SELECTION,
+  MORE_OPTIONS_SELECTION,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index fbec1bc..997519d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -19,9 +19,13 @@
 import androidx.compose.material.Divider
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
 import androidx.compose.material.ModalBottomSheetLayout
 import androidx.compose.material.ModalBottomSheetValue
 import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -71,8 +75,13 @@
           onOptionSelected = {viewModel.onCreateOptionSelected(it)},
           onCancel = cancelActivity,
           multiProvider = uiState.providers.size > 1,
-          onMoreOptionSelected = {viewModel.onMoreOptionSelected()}
+          onMoreOptionSelected = {viewModel.onMoreOptionSelected(it)}
         )
+        CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionSelectionCard(
+            providerInfo = uiState.selectedProvider!!,
+            onCancel = cancelActivity,
+            onBackButtonSelected = {viewModel.onBackButtonSelected(it)}
+          )
       }
     },
     scrimColor = Color.Transparent,
@@ -202,6 +211,67 @@
   }
 }
 
+@Composable
+fun MoreOptionSelectionCard(
+  providerInfo: ProviderInfo,
+  onCancel: () -> Unit,
+  onBackButtonSelected: (String) -> Unit
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      TopAppBar(
+        title = { Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1) },
+        backgroundColor = lightBackgroundColor,
+        elevation = 0.dp,
+        navigationIcon =
+        {
+          IconButton(onClick = { onBackButtonSelected(providerInfo.name) }) {
+            Icon(Icons.Filled.ArrowBack, "backIcon"
+            )
+          }
+        }
+      )
+      Divider(
+         thickness = 24.dp,
+         color = Color.Transparent
+      )
+      Text(
+        text = stringResource(R.string.create_passkey_at),
+        style = Typography.body1,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_cancel), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
 @ExperimentalMaterialApi
 @Composable
 fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
@@ -276,7 +346,7 @@
   onOptionSelected: (String) -> Unit,
   onCancel: () -> Unit,
   multiProvider: Boolean,
-  onMoreOptionSelected: () -> Unit,
+  onMoreOptionSelected: (String) -> Unit,
 ) {
   Card(
     backgroundColor = lightBackgroundColor,
@@ -318,7 +388,7 @@
           }
           if (multiProvider) {
             item {
-              MoreOptionRow(onSelect = onMoreOptionSelected)
+              MoreOptionRow(onSelect = { onMoreOptionSelected(providerInfo.name) })
             }
           }
         }
@@ -389,7 +459,7 @@
     shape = Shapes.large
   ) {
       Text(
-        text = stringResource(R.string.string_more_options),
+        text = stringResource(R.string.string_create_at_another_place),
         style = Typography.h6,
       )
   }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index e42016d..15300de 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -52,7 +52,17 @@
     }
   }
 
-  fun onMoreOptionSelected() {
-    Log.d("Account Selector", "On more option selected")
+  fun onMoreOptionSelected(providerName: String) {
+    uiState = uiState.copy(
+        currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+        selectedProvider = getProviderInfoByName(providerName)
+    )
+  }
+
+  fun onBackButtonSelected(providerName: String) {
+    uiState = uiState.copy(
+        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+        selectedProvider = getProviderInfoByName(providerName)
+    )
   }
 }
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 6669d6b..696ea4a 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -59,6 +59,10 @@
                 <action android:name="android.content.pm.action.CONFIRM_INSTALL" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            <intent-filter android:priority="1">
+                <action android:name="android.content.pm.action.CONFIRM_PRE_APPROVAL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <activity android:name=".InstallStaging"
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index ac1a574f..bfab9be 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -59,7 +59,8 @@
         String callingAttributionTag = null;
 
         final boolean isSessionInstall =
-                PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
+                PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
+                        || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
 
         // If the activity was started via a PackageInstaller session, we retrieve the calling
         // package from that session
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 20dc2cb..de76632 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -39,9 +39,11 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
+import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Process;
@@ -151,7 +153,7 @@
     }
 
     /**
-     * Replace any dialog shown by the dialog with the one for the given {@link #createDialog id}.
+     * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
      *
      * @param id The dialog type to add
      */
@@ -296,6 +298,14 @@
                 ? RESULT_OK : RESULT_FIRST_USER, result);
     }
 
+    private static PackageInfo generateStubPackageInfo(String packageName) {
+        final PackageInfo info = new PackageInfo();
+        final ApplicationInfo aInfo = new ApplicationInfo();
+        info.applicationInfo = aInfo;
+        info.packageName = info.applicationInfo.packageName = packageName;
+        return info;
+    }
+
     @Override
     protected void onCreate(Bundle icicle) {
         if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
@@ -315,6 +325,7 @@
         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
 
         final Intent intent = getIntent();
+        final String action = intent.getAction();
 
         mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
         mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
@@ -324,11 +335,11 @@
         mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
                 ? getPackageNameForUid(mOriginatingUid) : null;
 
-        final Uri packageUri;
-
-        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
-            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
-            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
+        final Object packageSource;
+        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(action)) {
+            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
+                    -1 /* defaultValue */);
+            final SessionInfo info = mInstaller.getSessionInfo(sessionId);
             if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
@@ -336,18 +347,32 @@
             }
 
             mSessionId = sessionId;
-            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
+            packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
+            mOriginatingURI = null;
+            mReferrerURI = null;
+        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
+            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
+                    -1 /* defaultValue */);
+            final SessionInfo info = mInstaller.getSessionInfo(sessionId);
+            if (info == null || !info.isPreapprovalRequested) {
+                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
+                finish();
+                return;
+            }
+
+            mSessionId = sessionId;
+            packageSource = info;
             mOriginatingURI = null;
             mReferrerURI = null;
         } else {
             mSessionId = -1;
-            packageUri = intent.getData();
+            packageSource = intent.getData();
             mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
             mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
         }
 
         // if there's nothing to do, quietly slip into the ether
-        if (packageUri == null) {
+        if (packageSource == null) {
             Log.w(TAG, "Unspecified source");
             setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
             finish();
@@ -359,7 +384,7 @@
             return;
         }
 
-        boolean wasSetUp = processPackageUri(packageUri);
+        final boolean wasSetUp = processAppSnippet(packageSource);
         if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
 
         if (!wasSetUp) {
@@ -619,6 +644,39 @@
         return true;
     }
 
+    /**
+     * Use the SessionInfo and set up the installer for pre-commit install session.
+     *
+     * @param info The SessionInfo to compose
+     *
+     * @return {@code true} iff the installer could be set up
+     */
+    private boolean processSessionInfo(@NonNull SessionInfo info) {
+        mPkgInfo = generateStubPackageInfo(info.appPackageName);
+        mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
+                info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
+                        : getPackageManager().getDefaultActivityIcon());
+        return true;
+    }
+
+    /**
+     * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
+     * session) to set up the installer for this install.
+     *
+     * @param source The source of package URI or SessionInfo
+     *
+     * @return {@code true} iff the installer could be set up
+     */
+    private boolean processAppSnippet(@NonNull Object source) {
+        if (source instanceof Uri) {
+            return processPackageUri((Uri) source);
+        } else if (source instanceof SessionInfo) {
+            return processSessionInfo((SessionInfo) source);
+        }
+
+        return false;
+    }
+
     @Override
     public void onBackPressed() {
         if (mSessionId != -1) {
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index e7e37e4..8edd5f7 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -30,7 +30,7 @@
     ],
     kotlincflags: [
         "-Xjvm-default=all",
-        "-Xopt-in=kotlin.RequiresOptIn",
+        "-opt-in=kotlin.RequiresOptIn",
     ],
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index 7796549..1dc52cb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -1,6 +1,7 @@
 package com.android.settingslib.spaprivileged.framework.common
 
 import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
 import android.content.Context
 import android.os.UserManager
 
@@ -9,3 +10,6 @@
 
 /** The [DevicePolicyManager] instance. */
 val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
+
+/** The [StorageStatsManager] instance. */
+val Context.storageStatsManager get() = getSystemService(StorageStatsManager::class.java)!!
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
new file mode 100644
index 0000000..037b737
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.text.format.Formatter
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun ApplicationInfo.getStorageSize(): State<String> {
+    val context = LocalContext.current
+    return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+        withContext(Dispatchers.IO) {
+            value = Formatter.formatFileSize(context, calculateSizeBytes(context))
+        }
+    }
+}
+
+private fun ApplicationInfo.calculateSizeBytes(context: Context): Long {
+    val storageStatsManager = context.storageStatsManager
+    val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
+    return stats.codeBytes + stats.dataBytes + stats.cacheBytes
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
new file mode 100644
index 0000000..cec6d7d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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.spaprivileged.template.app
+
+import android.app.usage.StorageStats
+import android.app.usage.StorageStatsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppStorageSizeTest {
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Spy
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var storageStatsManager: StorageStatsManager
+
+    private val app = ApplicationInfo().apply {
+        storageUuid = UUID.randomUUID()
+    }
+
+    @Before
+    fun setUp() {
+        whenever(context.storageStatsManager).thenReturn(storageStatsManager)
+        whenever(storageStatsManager.queryStatsForPackage(
+            app.storageUuid, app.packageName, app.userHandle
+        )).thenReturn(STATS)
+    }
+
+    @Test
+    fun getStorageSize() {
+        var storageSize = stateOf("")
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                storageSize = app.getStorageSize()
+            }
+        }
+
+        assertThat(storageSize.value).isEqualTo("123 B")
+    }
+
+    companion object {
+        private val STATS = StorageStats().apply {
+            codeBytes = 100
+            dataBytes = 20
+            cacheBytes = 3
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
index c2034f8..0c0f5a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
@@ -18,12 +18,9 @@
 
 import static java.util.Objects.requireNonNull;
 
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
 
@@ -69,50 +66,11 @@
         mResolveActivityChecker = requireNonNull(resolveActivityChecker);
     }
 
+    /**
+     * We don't show Learn More button in Admin-Support Dialog anymore.
+     */
     @Override
     public void setupLearnMoreButton(Context context) {
-        assertInitialized();
-
-        String url = mStringProvider.getLearnMoreHelpPageUrl();
-
-        if (!TextUtils.isEmpty(url)
-                && canLaunchHelpPageInPreferredOrCurrentUser(context, url, mPreferredUserHandle)) {
-            setupLearnMoreButtonToLaunchHelpPage(context, url, mPreferredUserHandle);
-        } else {
-            mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId,
-                    mEnforcedAdmin);
-        }
-    }
-
-    private boolean canLaunchHelpPageInPreferredOrCurrentUser(
-            Context context, String url, UserHandle preferredUserHandle) {
-        PackageManager packageManager = context.getPackageManager();
-        if (mLauncher.canLaunchHelpPage(
-                packageManager, url, preferredUserHandle, mResolveActivityChecker)
-                && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) {
-            return true;
-        }
-        return mLauncher.canLaunchHelpPage(
-                packageManager, url, context.getUser(), mResolveActivityChecker);
-    }
-
-    /**
-     * Sets up the "Learn more" button to launch the web help page in the {@code
-     * preferredUserHandle} user. If not possible to launch it there, it sets up the button to
-     * launch it in the current user instead.
-     */
-    private void setupLearnMoreButtonToLaunchHelpPage(
-            Context context, String url, UserHandle preferredUserHandle) {
-        PackageManager packageManager = context.getPackageManager();
-        if (mLauncher.canLaunchHelpPage(
-                packageManager, url, preferredUserHandle, mResolveActivityChecker)
-                && mForegroundUserChecker.isUserForeground(context, preferredUserHandle)) {
-            mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, preferredUserHandle);
-        }
-        if (mLauncher.canLaunchHelpPage(
-                packageManager, url, context.getUser(), mResolveActivityChecker)) {
-            mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url, context.getUser());
-        }
     }
 
     private static boolean isUserForeground(Context context, UserHandle userHandle) {
@@ -122,25 +80,7 @@
 
     @Override
     public String getAdminSupportTitle(@Nullable String restriction) {
-        if (restriction == null) {
-            return mStringProvider.getDefaultDisabledByPolicyTitle();
-        }
-        switch (restriction) {
-            case UserManager.DISALLOW_ADJUST_VOLUME:
-                return mStringProvider.getDisallowAdjustVolumeTitle();
-            case UserManager.DISALLOW_OUTGOING_CALLS:
-                return mStringProvider.getDisallowOutgoingCallsTitle();
-            case UserManager.DISALLOW_SMS:
-                return mStringProvider.getDisallowSmsTitle();
-            case DevicePolicyManager.POLICY_DISABLE_CAMERA:
-                return mStringProvider.getDisableCameraTitle();
-            case DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE:
-                return mStringProvider.getDisableScreenCaptureTitle();
-            case DevicePolicyManager.POLICY_SUSPEND_PACKAGES:
-                return mStringProvider.getSuspendPackagesTitle();
-            default:
-                return mStringProvider.getDefaultDisabledByPolicyTitle();
-        }
+        return mStringProvider.getDefaultDisabledByPolicyTitle();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 3e7481a..f5dddc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -43,6 +43,7 @@
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupdesign.GlifLayout;
 import com.google.android.setupdesign.util.ThemeHelper;
+import com.google.android.setupdesign.util.ThemeResolver;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,7 +82,14 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setTheme(R.style.SudThemeGlifV3_DayNight);
+        boolean dayNightEnabled = ThemeHelper.isSetupWizardDayNightEnabled(this);
+        ThemeResolver themeResolver =
+                new ThemeResolver.Builder(ThemeResolver.getDefault())
+                        .setDefaultTheme(ThemeHelper.getSuwDefaultTheme(this))
+                        .setUseDayNight(true)
+                        .build();
+        int themeResId = themeResolver.resolve("", /* suppressDayNight= */ !dayNightEnabled);
+        setTheme(themeResId);
         ThemeHelper.trySetDynamicColor(this);
         setContentView(R.layout.avatar_picker);
         setUpButtons();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index bc9bdec..7b88566 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -18,10 +18,7 @@
 
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_LAUNCH_HELP_PAGE;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES;
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE;
-import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_CONTENT;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DISALLOW_ADJUST_VOLUME_TITLE;
@@ -64,67 +61,6 @@
     }
 
     @Test
-    public void setupLearnMoreButton_noUrl_negativeButtonSet() {
-        ManagedDeviceActionDisabledByAdminController controller = createController(EMPTY_URL);
-
-        controller.setupLearnMoreButton(mContext);
-
-        mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
-    }
-
-    @Test
-    public void setupLearnMoreButton_validUrl_foregroundUser_launchesHelpPage() {
-        ManagedDeviceActionDisabledByAdminController controller = createController(
-                URL,
-                /* isUserForeground= */ true,
-                /* preferredUserHandle= */ MANAGED_USER,
-                /* userContainingBrowser= */ MANAGED_USER);
-
-        controller.setupLearnMoreButton(mContext);
-
-        mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE);
-    }
-
-    @Test
-    public void setupLearnMoreButton_validUrl_browserInPreferredUser_notForeground_showsAdminPolicies() {
-        ManagedDeviceActionDisabledByAdminController controller = createController(
-                URL,
-                /* isUserForeground= */ false,
-                /* preferredUserHandle= */ MANAGED_USER,
-                /* userContainingBrowser= */ MANAGED_USER);
-
-        controller.setupLearnMoreButton(mContext);
-
-        mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
-    }
-
-    @Test
-    public void setupLearnMoreButton_validUrl_browserInCurrentUser_launchesHelpPage() {
-        ManagedDeviceActionDisabledByAdminController controller = createController(
-                URL,
-                /* isUserForeground= */ false,
-                /* preferredUserHandle= */ MANAGED_USER,
-                /* userContainingBrowser= */ mContext.getUser());
-
-        controller.setupLearnMoreButton(mContext);
-
-        mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE);
-    }
-
-    @Test
-    public void setupLearnMoreButton_validUrl_browserNotOnAnyUser_showsAdminPolicies() {
-        ManagedDeviceActionDisabledByAdminController controller = createController(
-                URL,
-                /* isUserForeground= */ false,
-                /* preferredUserHandle= */ MANAGED_USER,
-                /* userContainingBrowser= */ null);
-
-        controller.setupLearnMoreButton(mContext);
-
-        mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
-    }
-
-    @Test
     public void getAdminSupportTitleResource_noRestriction_works() {
         ManagedDeviceActionDisabledByAdminController controller = createController();
 
@@ -137,7 +73,7 @@
         ManagedDeviceActionDisabledByAdminController controller = createController();
 
         assertThat(controller.getAdminSupportTitle(RESTRICTION))
-                .isEqualTo(SUPPORT_TITLE_FOR_RESTRICTION);
+                .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index fe337d267..8e33ca3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -106,13 +106,20 @@
             + "license content #1\n"
             + "</pre><!-- license-text -->\n"
             + "</td></tr><!-- same-license -->\n"
-            + "</table></body></html>\n";
+            + "</table>\n"
+            + "<div class=\"path-counts\"><table>\n"
+            + "  <tr><th>Path prefix</th><th>Count</th></tr>\n\n"
+            + "  <tr><td>file0</td><td>1</td></tr>\n"
+            + "  <tr><td>file1</td><td>1</td></tr>\n"
+            + "</table></div>\n\n"
+            + "</body></html>\n";
 
     private static final String HTML_NEW_BODY_STRING =
             "<strong>Libraries</strong>\n"
             + "<ul class=\"libraries\">\n"
             + "<li><a href=\"#id0\">libA</a></li>\n"
             + "<li><a href=\"#id1\">libB</a></li>\n"
+            + "<li><a href=\"#id0\">libC</a></li>\n"
             + "</ul>\n"
             + "<strong>Files</strong>\n"
             + "<ul class=\"files\">\n"
@@ -146,7 +153,14 @@
             + "license content #1\n"
             + "</pre><!-- license-text -->\n"
             + "</td></tr><!-- same-license -->\n"
-            + "</table></body></html>\n";
+            + "</table>\n"
+            + "<div class=\"path-counts\"><table>\n"
+            + "  <tr><th>Path prefix</th><th>Count</th></tr>\n\n"
+            + "  <tr><td>file0</td><td>1</td></tr>\n"
+            + "  <tr><td>file1</td><td>1</td></tr>\n"
+            + "  <tr><td>file2</td><td>1</td></tr>\n"
+            + "</table></div>\n\n"
+            + "</body></html>\n";
 
     private static final String EXPECTED_OLD_HTML_STRING = HTML_HEAD_STRING + HTML_OLD_BODY_STRING;
 
@@ -263,7 +277,7 @@
         Map<String, Set<String>> toOne = new HashMap<>();
 
         toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
-        toOne.put("", new HashSet<String>(Arrays.asList("0", "1")));
+        toOne.put("", new HashSet<String>(Arrays.asList("0")));
 
         fileNameToLibraryToContentIdMap.put("/file0", toBoth);
         fileNameToLibraryToContentIdMap.put("/file1", toOne);
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 3325eec..6d61fd8 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -5,20 +5,32 @@
 dsandler@android.com
 
 aaliomer@google.com
+aaronjli@google.com
+acul@google.com
 adamcohen@google.com
+aioana@google.com
 alexflo@google.com
+andonian@google.com
+aroederer@google.com
 arteiro@google.com
 asc@google.com
 awickham@google.com
+ayepin@google.com
+bbade@google.com
 beverlyt@google.com
-brzezinski@google.com
+bhinegardner@google.com
+bhnm@google.com
 brycelee@google.com
+brzezinski@google.com
 caitlinshk@google.com
+chandruis@google.com
 chrisgollner@google.com
+cinek@google.com
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
 florenceyang@google.com
+gallmann@google.com
 gwasserman@google.com
 hwwang@google.com
 hyunyoungs@google.com
@@ -35,31 +47,41 @@
 juliacr@google.com
 juliatuttle@google.com
 justinkoh@google.com
+justinweir@google.com
 kozynski@google.com
 kprevas@google.com
+lusilva@google.com
 lynhan@google.com
 madym@google.com
 mankoff@google.com
+mateuszc@google.com
+michaelmikhil@google.com
+michschn@google.com
 mkephart@google.com
 mpietal@google.com
 mrcasey@google.com
 mrenouf@google.com
 nickchameyev@google.com
 nicomazz@google.com
+nijamkin@google.com
 ogunwale@google.com
+omarmt@google.com
+patmanning@google.com
 peanutbutter@google.com
 peskal@google.com
 pinyaoting@google.com
 pixel@google.com
 pomini@google.com
 rahulbanerjee@google.com
+rasheedlewis@google.com
 roosa@google.com
+saff@google.com
 santie@google.com
 shanh@google.com
 snoeberger@google.com
 steell@google.com
-sfufa@google.com
 stwu@google.com
+syeonlee@google.com
 sunnygoyal@google.com
 susikp@google.com
 thiruram@google.com
@@ -70,10 +92,13 @@
 victortulias@google.com
 winsonc@google.com
 wleshner@google.com
+xilei@google.com
 xuqiu@google.com
+yeinj@google.com
 yuandizhou@google.com
 yurilin@google.com
 zakcohen@google.com
+zoepage@google.com
 
 #Android TV
 rgl@google.com
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index f7150ab..2d82307a 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -16,7 +16,6 @@
 -->
 <resources>
     <!-- DialogLaunchAnimator -->
-    <item type="id" name="tag_launch_animation_running"/>
     <item type="id" name="tag_dialog_background"/>
 
     <!-- ViewBoundsAnimator -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9656b8a..23cee4d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -29,12 +29,12 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewRootImpl
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.widget.FrameLayout
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.Configuration
 import com.android.internal.jank.InteractionJankMonitor.CujType
 import kotlin.math.roundToInt
 
@@ -46,6 +46,7 @@
  *
  * This animator also allows to easily animate a dialog into an activity.
  *
+ * @see show
  * @see showFromView
  * @see showFromDialog
  * @see createActivityLaunchController
@@ -67,8 +68,81 @@
             ActivityLaunchAnimator.INTERPOLATORS.copy(
                 positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
             )
+    }
 
-        private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running
+    /**
+     * A controller that takes care of applying the dialog launch and exit animations to the source
+     * that triggered the animation.
+     */
+    interface Controller {
+        /** The [ViewRootImpl] of this controller. */
+        val viewRoot: ViewRootImpl
+
+        /**
+         * The identity object of the source animated by this controller. This animator will ensure
+         * that 2 animations with the same source identity are not going to run at the same time, to
+         * avoid flickers when a dialog is shown from the same source more or less at the same time
+         * (for instance if the user clicks an expandable button twice).
+         */
+        val sourceIdentity: Any
+
+        /**
+         * Move the drawing of the source in the overlay of [viewGroup].
+         *
+         * Once this method is called, and until [stopDrawingInOverlay] is called, the source
+         * controlled by this Controller should be drawn in the overlay of [viewGroup] so that it is
+         * drawn above all other elements in the same [viewRoot].
+         */
+        fun startDrawingInOverlayOf(viewGroup: ViewGroup)
+
+        /**
+         * Move the drawing of the source back in its original location.
+         *
+         * @see startDrawingInOverlayOf
+         */
+        fun stopDrawingInOverlay()
+
+        /**
+         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * controlled by this [Controller] during the dialog launch animation.
+         *
+         * At the end of this animation, the source should *not* be visible anymore (until the
+         * dialog is closed and is animated back into the source).
+         */
+        fun createLaunchController(): LaunchAnimator.Controller
+
+        /**
+         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * controlled by this [Controller] during the dialog exit animation.
+         *
+         * At the end of this animation, the source should be visible again.
+         */
+        fun createExitController(): LaunchAnimator.Controller
+
+        /**
+         * Whether we should animate the dialog back into the source when it is dismissed. If this
+         * methods returns `false`, then the dialog will simply fade out and
+         * [onExitAnimationCancelled] will be called.
+         *
+         * Note that even when this returns `true`, the exit animation might still be cancelled (in
+         * which case [onExitAnimationCancelled] will also be called).
+         */
+        fun shouldAnimateExit(): Boolean
+
+        /**
+         * Called if we decided to *not* animate the dialog into the source for some reason. This
+         * means that [createExitController] will *not* be called and this implementation should
+         * make sure that the source is back in its original state, before it was animated into the
+         * dialog. In particular, the source should be visible again.
+         */
+        fun onExitAnimationCancelled()
+
+        /**
+         * Return the [InteractionJankMonitor.Configuration.Builder] to be used for animations
+         * controlled by this controller.
+         */
+        // TODO(b/252723237): Make this non-nullable
+        fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
     }
 
     /**
@@ -96,7 +170,28 @@
         dialog: Dialog,
         view: View,
         cuj: DialogCuj? = null,
-        animateBackgroundBoundsChange: Boolean = false,
+        animateBackgroundBoundsChange: Boolean = false
+    ) {
+        show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+    }
+
+    /**
+     * Show [dialog] by expanding it from a source controlled by [controller].
+     *
+     * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
+     * animated when the dialog bounds change.
+     *
+     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+     * animated.
+     *
+     * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
+     * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
+     */
+    fun show(
+        dialog: Dialog,
+        controller: Controller,
+        cuj: DialogCuj? = null,
+        animateBackgroundBoundsChange: Boolean = false
     ) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
             throw IllegalStateException(
@@ -109,9 +204,10 @@
         // intent is to launch a dialog from another dialog.
         val animatedParent =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
             }
-        val animateFrom = animatedParent?.dialogContentWithBackground ?: view
+        val animateFrom =
+            animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
 
         if (animatedParent == null && animateFrom !is LaunchableView) {
             // Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -126,15 +222,17 @@
             )
         }
 
-        // Make sure we don't run the launch animation from the same view twice at the same time.
-        if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
-            Log.e(TAG, "Not running dialog launch animation as there is already one running")
+        // Make sure we don't run the launch animation from the same source twice at the same time.
+        if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
+            Log.e(
+                TAG,
+                "Not running dialog launch animation from source as it is already expanded into a" +
+                    " dialog"
+            )
             dialog.show()
             return
         }
 
-        animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
-
         val animatedDialog =
             AnimatedDialog(
                 launchAnimator,
@@ -146,16 +244,99 @@
                 animateBackgroundBoundsChange,
                 animatedParent,
                 isForTesting,
-                cuj
+                cuj,
             )
 
         openedDialogs.add(animatedDialog)
         animatedDialog.start()
     }
 
+    /** Create a [Controller] that can animate [source] to & from a dialog. */
+    private fun createController(source: View): Controller {
+        return object : Controller {
+            override val viewRoot: ViewRootImpl
+                get() = source.viewRootImpl
+
+            override val sourceIdentity: Any = source
+
+            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+                // Create a temporary ghost of the source (which will make it invisible) and add it
+                // to the host dialog.
+                GhostView.addGhost(source, viewGroup)
+
+                // The ghost of the source was just created, so the source is currently invisible.
+                // We need to make sure that it stays invisible as long as the dialog is shown or
+                // animating.
+                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+            }
+
+            override fun stopDrawingInOverlay() {
+                // Note: here we should remove the ghost from the overlay, but in practice this is
+                // already done by the launch controllers created below.
+
+                // Make sure we allow the source to change its visibility again.
+                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+                source.visibility = View.VISIBLE
+            }
+
+            override fun createLaunchController(): LaunchAnimator.Controller {
+                val delegate = GhostedViewLaunchAnimatorController(source)
+                return object : LaunchAnimator.Controller by delegate {
+                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                        // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+                        // ghost (that ghosts only the source content, and not its background) will
+                        // be added right after this by the delegate and will be animated.
+                        GhostView.removeGhost(source)
+                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                    }
+
+                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+                        // We hide the source when the dialog is showing. We will make this view
+                        // visible again when dismissing the dialog. This does nothing if the source
+                        // implements [LaunchableView], as it's already INVISIBLE in that case.
+                        source.visibility = View.INVISIBLE
+                    }
+                }
+            }
+
+            override fun createExitController(): LaunchAnimator.Controller {
+                return GhostedViewLaunchAnimatorController(source)
+            }
+
+            override fun shouldAnimateExit(): Boolean {
+                // The source should be invisible by now, if it's not then something else changed
+                // its visibility and we probably don't want to run the animation.
+                if (source.visibility != View.INVISIBLE) {
+                    return false
+                }
+
+                return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+            }
+
+            override fun onExitAnimationCancelled() {
+                // Make sure we allow the source to change its visibility again.
+                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+                // If the view is invisible it's probably because of us, so we make it visible
+                // again.
+                if (source.visibility == View.INVISIBLE) {
+                    source.visibility = View.VISIBLE
+                }
+            }
+
+            override fun jankConfigurationBuilder(
+                cuj: Int
+            ): InteractionJankMonitor.Configuration.Builder? {
+                return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
+            }
+        }
+    }
+
     /**
-     * Launch [dialog] from [another dialog][animateFrom] that was shown using [showFromView]. This
-     * will allow for dismissing the whole stack.
+     * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
+     * allow for dismissing the whole stack.
      *
      * @see dismissStack
      */
@@ -181,32 +362,55 @@
 
     /**
      * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
-     * dialog that contains [View]. Note that the dialog must have been show using [showFromView]
-     * and be currently showing, otherwise this will return null.
+     * dialog that contains [View]. Note that the dialog must have been shown using this animator,
+     * otherwise this method will return null.
      *
      * The returned controller will take care of dismissing the dialog at the right time after the
      * activity started, when the dialog to app animation is done (or when it is cancelled). If this
      * method returns null, then the dialog won't be dismissed.
      *
-     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
-     * animated.
-     *
      * @param view any view inside the dialog to animate.
      */
     @JvmOverloads
     fun createActivityLaunchController(
         view: View,
-        cujType: Int? = null
+        cujType: Int? = null,
     ): ActivityLaunchAnimator.Controller? {
         val animatedDialog =
             openedDialogs.firstOrNull {
                 it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
             }
                 ?: return null
+        return createActivityLaunchController(animatedDialog, cujType)
+    }
 
+    /**
+     * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from
+     * [dialog]. Note that the dialog must have been shown using this animator, otherwise this
+     * method will return null.
+     *
+     * The returned controller will take care of dismissing the dialog at the right time after the
+     * activity started, when the dialog to app animation is done (or when it is cancelled). If this
+     * method returns null, then the dialog won't be dismissed.
+     *
+     * @param dialog the dialog to animate.
+     */
+    @JvmOverloads
+    fun createActivityLaunchController(
+        dialog: Dialog,
+        cujType: Int? = null,
+    ): ActivityLaunchAnimator.Controller? {
+        val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null
+        return createActivityLaunchController(animatedDialog, cujType)
+    }
+
+    private fun createActivityLaunchController(
+        animatedDialog: AnimatedDialog,
+        cujType: Int? = null
+    ): ActivityLaunchAnimator.Controller? {
         // At this point, we know that the intent of the caller is to dismiss the dialog to show
-        // an app, so we disable the exit animation into the touch surface because we will never
-        // want to run it anyways.
+        // an app, so we disable the exit animation into the source because we will never want to
+        // run it anyways.
         animatedDialog.exitAnimationDisabled = true
 
         val dialog = animatedDialog.dialog
@@ -252,7 +456,7 @@
 
                 // If this dialog was shown from a cascade of other dialogs, make sure those ones
                 // are dismissed too.
-                animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
+                animatedDialog.prepareForStackDismiss()
 
                 // Remove the dim.
                 dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
@@ -283,12 +487,11 @@
     }
 
     /**
-     * Ensure that all dialogs currently shown won't animate into their touch surface when
-     * dismissed.
+     * Ensure that all dialogs currently shown won't animate into their source when dismissed.
      *
      * This is a temporary API meant to be called right before we both dismiss a dialog and start an
-     * activity, which currently does not look good if we animate the dialog into the touch surface
-     * at the same time as the activity starts.
+     * activity, which currently does not look good if we animate the dialog into their source at
+     * the same time as the activity starts.
      *
      * TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
      */
@@ -297,13 +500,11 @@
     }
 
     /**
-     * Dismiss [dialog]. If it was launched from another dialog using [showFromView], also dismiss
-     * the stack of dialogs, animating back to the original touchSurface.
+     * Dismiss [dialog]. If it was launched from another dialog using this animator, also dismiss
+     * the stack of dialogs and simply fade out [dialog].
      */
     fun dismissStack(dialog: Dialog) {
-        openedDialogs
-            .firstOrNull { it.dialog == dialog }
-            ?.let { it.touchSurface = it.prepareForStackDismiss() }
+        openedDialogs.firstOrNull { it.dialog == dialog }?.prepareForStackDismiss()
         dialog.dismiss()
     }
 
@@ -337,8 +538,11 @@
     private val callback: DialogLaunchAnimator.Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
 
-    /** The view that triggered the dialog after being tapped. */
-    var touchSurface: View,
+    /**
+     * The controller of the source that triggered the dialog and that will animate into/from the
+     * dialog.
+     */
+    val controller: DialogLaunchAnimator.Controller,
 
     /**
      * A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and
@@ -383,17 +587,18 @@
     private var originalDialogBackgroundColor = Color.BLACK
 
     /**
-     * Whether we are currently launching/showing the dialog by animating it from [touchSurface].
+     * Whether we are currently launching/showing the dialog by animating it from its source
+     * controlled by [controller].
      */
     private var isLaunching = true
 
-    /** Whether we are currently dismissing/hiding the dialog by animating into [touchSurface]. */
+    /** Whether we are currently dismissing/hiding the dialog by animating into its source. */
     private var isDismissing = false
 
     private var dismissRequested = false
     var exitAnimationDisabled = false
 
-    private var isTouchSurfaceGhostDrawn = false
+    private var isSourceDrawnInDialog = false
     private var isOriginalDialogViewLaidOut = false
 
     /** A layout listener to animate the dialog height change. */
@@ -410,13 +615,19 @@
      */
     private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
 
+    private var hasInstrumentedJank = false
+
     fun start() {
         if (cuj != null) {
-            val config = Configuration.Builder.withView(cuj.cujType, touchSurface)
-            if (cuj.tag != null) {
-                config.setTag(cuj.tag)
+            val config = controller.jankConfigurationBuilder(cuj.cujType)
+            if (config != null) {
+                if (cuj.tag != null) {
+                    config.setTag(cuj.tag)
+                }
+
+                interactionJankMonitor.begin(config)
+                hasInstrumentedJank = true
             }
-            interactionJankMonitor.begin(config)
         }
 
         // Create the dialog so that its onCreate() method is called, which usually sets the dialog
@@ -618,47 +829,45 @@
         // Show the dialog.
         dialog.show()
 
-        addTouchSurfaceGhost()
+        moveSourceDrawingToDialog()
     }
 
-    private fun addTouchSurfaceGhost() {
+    private fun moveSourceDrawingToDialog() {
         if (decorView.viewRootImpl == null) {
-            // Make sure that we have access to the dialog view root to synchronize the creation of
-            // the ghost.
-            decorView.post(::addTouchSurfaceGhost)
+            // Make sure that we have access to the dialog view root to move the drawing to the
+            // dialog overlay.
+            decorView.post(::moveSourceDrawingToDialog)
             return
         }
 
-        // Create a ghost of the touch surface (which will make the touch surface invisible) and add
-        // it to the host dialog. We trigger a one off synchronization to make sure that this is
-        // done in sync between the two different windows.
+        // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
+        // one-off synchronization to make sure that this is done in sync between the two different
+        // windows.
         synchronizeNextDraw(
             then = {
-                isTouchSurfaceGhostDrawn = true
+                isSourceDrawnInDialog = true
                 maybeStartLaunchAnimation()
             }
         )
-        GhostView.addGhost(touchSurface, decorView)
-
-        // The ghost of the touch surface was just created, so the touch surface is currently
-        // invisible. We need to make sure that it stays invisible as long as the dialog is shown or
-        // animating.
-        (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+        controller.startDrawingInOverlayOf(decorView)
     }
 
     /**
-     * Synchronize the next draw of the touch surface and dialog view roots so that they are
-     * performed at the same time, in the same transaction. This is necessary to make sure that the
-     * ghost of the touch surface is drawn at the same time as the touch surface is made invisible
-     * (or inversely, removed from the UI when the touch surface is made visible).
+     * Synchronize the next draw of the source and dialog view roots so that they are performed at
+     * the same time, in the same transaction. This is necessary to make sure that the source is
+     * drawn in the overlay at the same time as it is removed from its original position (or
+     * inversely, removed from the overlay when the source is moved back to its original position).
      */
     private fun synchronizeNextDraw(then: () -> Unit) {
         if (forceDisableSynchronization) {
+            // Don't synchronize when inside an automated test.
             then()
             return
         }
 
-        ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then)
+        ViewRootSync.synchronizeNextDraw(decorView, controller.viewRoot.view, then)
+        decorView.invalidate()
+        controller.viewRoot.view.invalidate()
     }
 
     private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -681,7 +890,7 @@
     }
 
     private fun maybeStartLaunchAnimation() {
-        if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+        if (!isSourceDrawnInDialog || !isOriginalDialogViewLaidOut) {
             return
         }
 
@@ -690,19 +899,7 @@
 
         startAnimation(
             isLaunching = true,
-            onLaunchAnimationStart = {
-                // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
-                // content, and not its background) will be added right after this and will be
-                // animated.
-                GhostView.removeGhost(touchSurface)
-            },
             onLaunchAnimationEnd = {
-                touchSurface.setTag(R.id.tag_launch_animation_running, null)
-
-                // We hide the touch surface when the dialog is showing. We will make this view
-                // visible again when dismissing the dialog.
-                touchSurface.visibility = View.INVISIBLE
-
                 isLaunching = false
 
                 // dismiss was called during the animation, dismiss again now to actually dismiss.
@@ -718,7 +915,10 @@
                         backgroundLayoutListener
                     )
                 }
-                cuj?.run { interactionJankMonitor.end(cujType) }
+
+                if (hasInstrumentedJank) {
+                    interactionJankMonitor.end(cuj!!.cujType)
+                }
             }
         )
     }
@@ -753,8 +953,8 @@
     }
 
     /**
-     * Hide the dialog into the touch surface and call [onAnimationFinished] when the animation is
-     * done (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
+     * Hide the dialog into the source and call [onAnimationFinished] when the animation is done
+     * (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually
      * dismiss the dialog.
      */
     private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) {
@@ -763,17 +963,9 @@
             decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
         }
 
-        if (!shouldAnimateDialogIntoView()) {
-            Log.i(TAG, "Skipping animation of dialog into the touch surface")
-
-            // Make sure we allow the touch surface to change its visibility again.
-            (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
-            // If the view is invisible it's probably because of us, so we make it visible again.
-            if (touchSurface.visibility == View.INVISIBLE) {
-                touchSurface.visibility = View.VISIBLE
-            }
-
+        if (!shouldAnimateDialogIntoSource()) {
+            Log.i(TAG, "Skipping animation of dialog into the source")
+            controller.onExitAnimationCancelled()
             onAnimationFinished(false /* instantDismiss */)
             onDialogDismissed(this@AnimatedDialog)
             return
@@ -786,10 +978,6 @@
                 dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             },
             onLaunchAnimationEnd = {
-                // Make sure we allow the touch surface to change its visibility again.
-                (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
-                touchSurface.visibility = View.VISIBLE
                 val dialogContentWithBackground = this.dialogContentWithBackground!!
                 dialogContentWithBackground.visibility = View.INVISIBLE
 
@@ -799,14 +987,11 @@
                     )
                 }
 
-                // Make sure that the removal of the ghost and making the touch surface visible is
-                // done at the same time.
-                synchronizeNextDraw(
-                    then = {
-                        onAnimationFinished(true /* instantDismiss */)
-                        onDialogDismissed(this@AnimatedDialog)
-                    }
-                )
+                controller.stopDrawingInOverlay()
+                synchronizeNextDraw {
+                    onAnimationFinished(true /* instantDismiss */)
+                    onDialogDismissed(this@AnimatedDialog)
+                }
             }
         )
     }
@@ -816,27 +1001,34 @@
         onLaunchAnimationStart: () -> Unit = {},
         onLaunchAnimationEnd: () -> Unit = {}
     ) {
-        // Create 2 ghost controllers to animate both the dialog and the touch surface in the
-        // dialog.
-        val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
-        val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
-        val startViewController = GhostedViewLaunchAnimatorController(startView)
-        val endViewController = GhostedViewLaunchAnimatorController(endView)
-        startViewController.launchContainer = decorView
-        endViewController.launchContainer = decorView
+        // Create 2 controllers to animate both the dialog and the source.
+        val startController =
+            if (isLaunching) {
+                controller.createLaunchController()
+            } else {
+                GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+            }
+        val endController =
+            if (isLaunching) {
+                GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+            } else {
+                controller.createExitController()
+            }
+        startController.launchContainer = decorView
+        endController.launchContainer = decorView
 
-        val endState = endViewController.createAnimatorState()
+        val endState = endController.createAnimatorState()
         val controller =
             object : LaunchAnimator.Controller {
                 override var launchContainer: ViewGroup
-                    get() = startViewController.launchContainer
+                    get() = startController.launchContainer
                     set(value) {
-                        startViewController.launchContainer = value
-                        endViewController.launchContainer = value
+                        startController.launchContainer = value
+                        endController.launchContainer = value
                     }
 
                 override fun createAnimatorState(): LaunchAnimator.State {
-                    return startViewController.createAnimatorState()
+                    return startController.createAnimatorState()
                 }
 
                 override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -845,15 +1037,29 @@
                     // onLaunchAnimationStart on the controller (which will create its own ghost).
                     onLaunchAnimationStart()
 
-                    startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
-                    endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+                    startController.onLaunchAnimationStart(isExpandingFullyAbove)
+                    endController.onLaunchAnimationStart(isExpandingFullyAbove)
                 }
 
                 override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                    startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
-                    endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
+                    // on a Choreographer animation tick. The following calls will move the animated
+                    // content from the dialog overlay back to its original position, and this
+                    // change must be reflected in the next frame given that we then sync the next
+                    // frame of both the content and dialog ViewRoots. However, in case that content
+                    // is rendered by Compose, whose compositions are also scheduled on a
+                    // Choreographer frame, any state change made *right now* won't be reflected in
+                    // the next frame given that a Choreographer frame can't schedule another and
+                    // have it happen in the same frame. So we post the forwarded calls to
+                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
+                    // that the move of the content back to its original window will be reflected in
+                    // the next frame right after [onLaunchAnimationEnd] is called.
+                    dialog.context.mainExecutor.execute {
+                        startController.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        endController.onLaunchAnimationEnd(isExpandingFullyAbove)
 
-                    onLaunchAnimationEnd()
+                        onLaunchAnimationEnd()
+                    }
                 }
 
                 override fun onLaunchAnimationProgress(
@@ -861,11 +1067,11 @@
                     progress: Float,
                     linearProgress: Float
                 ) {
-                    startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    startController.onLaunchAnimationProgress(state, progress, linearProgress)
 
                     // The end view is visible only iff the starting view is not visible.
                     state.visible = !state.visible
-                    endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    endController.onLaunchAnimationProgress(state, progress, linearProgress)
 
                     // If the dialog content is complex, its dimension might change during the
                     // launch animation. The animation end position might also change during the
@@ -873,14 +1079,16 @@
                     // Therefore we update the end state to the new position/size. Usually the
                     // dialog dimension or position will change in the early frames, so changing the
                     // end state shouldn't really be noticeable.
-                    endViewController.fillGhostedViewState(endState)
+                    if (endController is GhostedViewLaunchAnimatorController) {
+                        endController.fillGhostedViewState(endState)
+                    }
                 }
             }
 
         launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
     }
 
-    private fun shouldAnimateDialogIntoView(): Boolean {
+    private fun shouldAnimateDialogIntoSource(): Boolean {
         // Don't animate if the dialog was previously hidden using hide() or if we disabled the exit
         // animation.
         if (exitAnimationDisabled || !dialog.isShowing) {
@@ -888,24 +1096,12 @@
         }
 
         // If we are dreaming, the dialog was probably closed because of that so we don't animate
-        // into the touchSurface.
+        // into the source.
         if (callback.isDreaming()) {
             return false
         }
 
-        // The touch surface should be invisible by now, if it's not then something else changed its
-        // visibility and we probably don't want to run the animation.
-        if (touchSurface.visibility != View.INVISIBLE) {
-            return false
-        }
-
-        // If the touch surface is not attached or one of its ancestors is not visible, then we
-        // don't run the animation either.
-        if (!touchSurface.isAttachedToWindow) {
-            return false
-        }
-
-        return (touchSurface.parent as? View)?.isShown ?: true
+        return controller.shouldAnimateExit()
     }
 
     /** A layout listener to animate the change of bounds of the dialog background. */
@@ -988,17 +1184,13 @@
         }
     }
 
-    fun prepareForStackDismiss(): View {
+    fun prepareForStackDismiss() {
         if (parentAnimatedDialog == null) {
-            return touchSurface
+            return
         }
         parentAnimatedDialog.exitAnimationDisabled = true
         parentAnimatedDialog.dialog.hide()
-        val view = parentAnimatedDialog.prepareForStackDismiss()
+        parentAnimatedDialog.prepareForStackDismiss()
         parentAnimatedDialog.dialog.dismiss()
-        // Make the touch surface invisible, so we end up animating to it when we actually
-        // dismiss the stack
-        view.visibility = View.INVISIBLE
-        return view
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index eb000ad..0028d13 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -199,6 +199,10 @@
         // the content before fading out the background.
         ghostView = GhostView.addGhost(ghostedView, launchContainer)
 
+        // The ghost was just created, so ghostedView is currently invisible. We need to make sure
+        // that it stays invisible as long as we are animating.
+        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
@@ -293,6 +297,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
+        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
         launchContainerOverlay.remove(backgroundView)
 
         // Make sure that the view is considered VISIBLE by accessibility by first making it
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 4cfe392..fbdb526 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,8 +30,11 @@
     ],
 
     static_libs: [
+        "SystemUIAnimationLib",
+
         "androidx.compose.runtime_runtime",
         "androidx.compose.material3_material3",
+        "androidx.savedstate_savedstate",
     ],
 
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
new file mode 100644
index 0000000..edbd684
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2022 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.compose.animation
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Density
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.min
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * Example:
+ * ```
+ *    Expandable(
+ *      color = MaterialTheme.colorScheme.primary,
+ *      shape = RoundedCornerShape(16.dp),
+ *    ) { controller ->
+ *      Row(
+ *        Modifier
+ *          // For activities:
+ *          .clickable { activityStarter.startActivity(intent, controller.forActivity()) }
+ *
+ *          // For dialogs:
+ *          .clickable { dialogLaunchAnimator.show(dialog, controller.forDialog()) }
+ *      ) { ... }
+ *    }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+    color: Color,
+    shape: Shape,
+    modifier: Modifier = Modifier,
+    contentColor: Color = contentColorFor(color),
+    content: @Composable (ExpandableController) -> Unit,
+) {
+    Expandable(
+        rememberExpandableController(color, shape, contentColor),
+        modifier,
+        content,
+    )
+}
+
+/**
+ * Create an expandable shape that can launch into an Activity or a Dialog.
+ *
+ * This overload can be used in cases where you need to create the [ExpandableController] before
+ * composing this [Expandable], for instance if something outside of this Expandable can trigger a
+ * launch animation
+ *
+ * Example:
+ * ```
+ *    // The controller that you can use to trigger the animations from anywhere.
+ *    val controller =
+ *        rememberExpandableController(
+ *          color = MaterialTheme.colorScheme.primary,
+ *          shape = RoundedCornerShape(16.dp),
+ *        )
+ *
+ *    Expandable(controller) {
+ *       ...
+ *    }
+ * ```
+ *
+ * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
+ * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ */
+@Composable
+fun Expandable(
+    controller: ExpandableController,
+    modifier: Modifier = Modifier,
+    content: @Composable (ExpandableController) -> Unit,
+) {
+    val controller = controller as ExpandableControllerImpl
+    val color = controller.color
+    val contentColor = controller.contentColor
+    val shape = controller.shape
+
+    // TODO(b/230830644): Use movableContentOf to preserve the content state instead once the
+    // Compose libraries have been updated and include aosp/2163631.
+    val wrappedContent =
+        @Composable { controller: ExpandableController ->
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor,
+            ) {
+                content(controller)
+            }
+        }
+
+    val thisExpandableSize by remember {
+        derivedStateOf { controller.boundsInComposeViewRoot.value.size }
+    }
+
+    // Make sure we don't read animatorState directly here to avoid recomposition every time the
+    // state changes (i.e. every frame of the animation).
+    val isAnimating by remember {
+        derivedStateOf {
+            controller.animatorState.value != null && controller.overlay.value != null
+        }
+    }
+
+    when {
+        isAnimating -> {
+            // Don't compose the movable content during the animation, as it should be composed only
+            // once at all times. We make this spacer exactly the same size as this Expandable when
+            // it is visible.
+            Spacer(
+                modifier
+                    .clip(shape)
+                    .requiredSize(with(controller.density) { thisExpandableSize.toDpSize() })
+            )
+
+            // The content and its animated background in the overlay. We draw it only when we are
+            // animating.
+            AnimatedContentInOverlay(
+                color,
+                thisExpandableSize,
+                controller.animatorState,
+                controller.overlay.value
+                    ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
+                controller,
+                wrappedContent,
+                controller.composeViewRoot,
+                { controller.currentComposeViewInOverlay.value = it },
+                controller.density,
+            )
+        }
+        controller.isDialogShowing.value -> {
+            Box(
+                modifier
+                    .drawWithContent { /* Don't draw anything when the dialog is shown. */}
+                    .onGloballyPositioned {
+                        controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+                    }
+            ) { wrappedContent(controller) }
+        }
+        else -> {
+            Box(
+                modifier.clip(shape).background(color, shape).onGloballyPositioned {
+                    controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+                }
+            ) { wrappedContent(controller) }
+        }
+    }
+}
+
+/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
+@Composable
+private fun AnimatedContentInOverlay(
+    color: Color,
+    sizeInOriginalLayout: Size,
+    animatorState: State<LaunchAnimator.State?>,
+    overlay: ViewGroupOverlay,
+    controller: ExpandableController,
+    content: @Composable (ExpandableController) -> Unit,
+    composeViewRoot: View,
+    onOverlayComposeViewChanged: (View?) -> Unit,
+    density: Density,
+) {
+    val compositionContext = rememberCompositionContext()
+    val context = LocalContext.current
+
+    // Create the ComposeView and force its content composition so that the movableContent is
+    // composed exactly once when we start animating.
+    val composeViewInOverlay =
+        remember(context, density) {
+            val startWidth = sizeInOriginalLayout.width
+            val startHeight = sizeInOriginalLayout.height
+            val contentModifier =
+                Modifier
+                    // Draw the content with the same size as it was at the start of the animation
+                    // so that its content is laid out exactly the same way.
+                    .requiredSize(with(density) { sizeInOriginalLayout.toDpSize() })
+                    .drawWithContent {
+                        val animatorState = animatorState.value ?: return@drawWithContent
+
+                        // Scale the content with the background while keeping its aspect ratio.
+                        val widthRatio =
+                            if (startWidth != 0f) {
+                                animatorState.width.toFloat() / startWidth
+                            } else {
+                                1f
+                            }
+                        val heightRatio =
+                            if (startHeight != 0f) {
+                                animatorState.height.toFloat() / startHeight
+                            } else {
+                                1f
+                            }
+                        val scale = min(widthRatio, heightRatio)
+                        scale(scale) { this@drawWithContent.drawContent() }
+                    }
+
+            val composeView =
+                ComposeView(context).apply {
+                    setContent {
+                        Box(
+                            Modifier.fillMaxSize().drawWithContent {
+                                val animatorState = animatorState.value ?: return@drawWithContent
+                                if (!animatorState.visible) {
+                                    return@drawWithContent
+                                }
+
+                                val topRadius = animatorState.topCornerRadius
+                                val bottomRadius = animatorState.bottomCornerRadius
+                                if (topRadius == bottomRadius) {
+                                    // Shortcut to avoid Outline calculation and allocation.
+                                    val cornerRadius = CornerRadius(topRadius)
+                                    drawRoundRect(color, cornerRadius = cornerRadius)
+                                } else {
+                                    val shape =
+                                        RoundedCornerShape(
+                                            topStart = topRadius,
+                                            topEnd = topRadius,
+                                            bottomStart = bottomRadius,
+                                            bottomEnd = bottomRadius,
+                                        )
+                                    val outline = shape.createOutline(size, layoutDirection, this)
+                                    drawOutline(outline, color = color)
+                                }
+
+                                drawContent()
+                            },
+                            // We center the content in the expanding container.
+                            contentAlignment = Alignment.Center,
+                        ) {
+                            Box(contentModifier) { content(controller) }
+                        }
+                    }
+                }
+
+            // Set the owners.
+            val overlayViewGroup =
+                getOverlayViewGroup(
+                    context,
+                    overlay,
+                )
+            ViewTreeLifecycleOwner.set(
+                overlayViewGroup,
+                ViewTreeLifecycleOwner.get(composeViewRoot),
+            )
+            ViewTreeViewModelStoreOwner.set(
+                overlayViewGroup,
+                ViewTreeViewModelStoreOwner.get(composeViewRoot),
+            )
+            overlayViewGroup.setViewTreeSavedStateRegistryOwner(
+                composeViewRoot.findViewTreeSavedStateRegistryOwner()
+            )
+
+            composeView.setParentCompositionContext(compositionContext)
+
+            composeView
+        }
+
+    DisposableEffect(overlay, composeViewInOverlay) {
+        // Add the ComposeView to the overlay.
+        overlay.add(composeViewInOverlay)
+
+        val startState =
+            animatorState.value
+                ?: throw IllegalStateException(
+                    "AnimatedContentInOverlay shouldn't be composed with null animatorState."
+                )
+        measureAndLayoutComposeViewInOverlay(composeViewInOverlay, startState)
+        onOverlayComposeViewChanged(composeViewInOverlay)
+
+        onDispose {
+            composeViewInOverlay.disposeComposition()
+            overlay.remove(composeViewInOverlay)
+            onOverlayComposeViewChanged(null)
+        }
+    }
+}
+
+internal fun measureAndLayoutComposeViewInOverlay(
+    view: View,
+    state: LaunchAnimator.State,
+) {
+    val exactWidth = state.width
+    val exactHeight = state.height
+    view.measure(
+        View.MeasureSpec.makeSafeMeasureSpec(exactWidth, View.MeasureSpec.EXACTLY),
+        View.MeasureSpec.makeSafeMeasureSpec(exactHeight, View.MeasureSpec.EXACTLY),
+    )
+
+    val parent = view.parent as ViewGroup
+    val parentLocation = parent.locationOnScreen
+    val offsetX = parentLocation[0]
+    val offsetY = parentLocation[1]
+    view.layout(
+        state.left - offsetX,
+        state.top - offsetY,
+        state.right - offsetX,
+        state.bottom - offsetY,
+    )
+}
+
+// TODO(b/230830644): Add hidden API to ViewGroupOverlay to access this ViewGroup directly?
+private fun getOverlayViewGroup(context: Context, overlay: ViewGroupOverlay): ViewGroup {
+    val view = View(context)
+    overlay.add(view)
+    var current = view.parent
+    while (current.parent != null) {
+        current = current.parent
+    }
+    overlay.remove(view)
+    return current as ViewGroup
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
new file mode 100644
index 0000000..065c314
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 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.compose.animation
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import android.view.ViewRootImpl
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.roundToInt
+
+/** A controller that can control animated launches. */
+interface ExpandableController {
+    /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
+    fun forActivity(): ActivityLaunchAnimator.Controller
+
+    /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
+    fun forDialog(): DialogLaunchAnimator.Controller
+}
+
+/**
+ * Create an [ExpandableController] to control an [Expandable]. This is useful if you need to create
+ * the controller before the [Expandable], for instance to handle clicks outside of the Expandable
+ * that would still trigger a dialog/activity launch animation.
+ */
+@Composable
+fun rememberExpandableController(
+    color: Color,
+    shape: Shape,
+    contentColor: Color = contentColorFor(color),
+): ExpandableController {
+    val composeViewRoot = LocalView.current
+    val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
+
+    // The current animation state, if we are currently animating a dialog or activity.
+    val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+
+    // Whether a dialog controlled by this ExpandableController is currently showing.
+    val isDialogShowing = remember { mutableStateOf(false) }
+
+    // The overlay in which we should animate the launch.
+    val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) }
+
+    // The current [ComposeView] being animated in the [overlay], if any.
+    val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) }
+
+    // The bounds in [composeViewRoot] of the expandable controlled by this controller.
+    val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) }
+
+    // Whether this composable is still composed. We only do the dialog exit animation if this is
+    // true.
+    val isComposed = remember { mutableStateOf(true) }
+    DisposableEffect(Unit) { onDispose { isComposed.value = false } }
+
+    return remember(color, contentColor, shape, composeViewRoot, density, layoutDirection) {
+        ExpandableControllerImpl(
+            color,
+            contentColor,
+            shape,
+            composeViewRoot,
+            density,
+            animatorState,
+            isDialogShowing,
+            overlay,
+            currentComposeViewInOverlay,
+            boundsInComposeViewRoot,
+            layoutDirection,
+            isComposed,
+        )
+    }
+}
+
+internal class ExpandableControllerImpl(
+    internal val color: Color,
+    internal val contentColor: Color,
+    internal val shape: Shape,
+    internal val composeViewRoot: View,
+    internal val density: Density,
+    internal val animatorState: MutableState<LaunchAnimator.State?>,
+    internal val isDialogShowing: MutableState<Boolean>,
+    internal val overlay: MutableState<ViewGroupOverlay?>,
+    internal val currentComposeViewInOverlay: MutableState<View?>,
+    internal val boundsInComposeViewRoot: MutableState<Rect>,
+    private val layoutDirection: LayoutDirection,
+    private val isComposed: State<Boolean>,
+) : ExpandableController {
+    override fun forActivity(): ActivityLaunchAnimator.Controller {
+        return activityController()
+    }
+
+    override fun forDialog(): DialogLaunchAnimator.Controller {
+        return dialogController()
+    }
+
+    /**
+     * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
+     * animation. This controller will:
+     * 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
+     * composeViewRoot on the screen.
+     * 2. Update [animatorState] with the current animation state if we are animating, or null
+     * otherwise.
+     */
+    private fun launchController(): LaunchAnimator.Controller {
+        return object : LaunchAnimator.Controller {
+            private val rootLocationOnScreen = intArrayOf(0, 0)
+
+            override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+
+            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                animatorState.value = null
+            }
+
+            override fun onLaunchAnimationProgress(
+                state: LaunchAnimator.State,
+                progress: Float,
+                linearProgress: Float
+            ) {
+                // We copy state given that it's always the same object that is mutated by
+                // ActivityLaunchAnimator.
+                animatorState.value =
+                    LaunchAnimator.State(
+                            state.top,
+                            state.bottom,
+                            state.left,
+                            state.right,
+                            state.topCornerRadius,
+                            state.bottomCornerRadius,
+                        )
+                        .apply { visible = state.visible }
+
+                // Force measure and layout the ComposeView in the overlay whenever the animation
+                // state changes.
+                currentComposeViewInOverlay.value?.let {
+                    measureAndLayoutComposeViewInOverlay(it, state)
+                }
+            }
+
+            override fun createAnimatorState(): LaunchAnimator.State {
+                val boundsInRoot = boundsInComposeViewRoot.value
+                val outline =
+                    shape.createOutline(
+                        Size(boundsInRoot.width, boundsInRoot.height),
+                        layoutDirection,
+                        density,
+                    )
+
+                val (topCornerRadius, bottomCornerRadius) =
+                    when (outline) {
+                        is Outline.Rectangle -> 0f to 0f
+                        is Outline.Rounded -> {
+                            val roundRect = outline.roundRect
+
+                            // TODO(b/230830644): Add better support different corner radii.
+                            val topCornerRadius =
+                                maxOf(
+                                    roundRect.topLeftCornerRadius.x,
+                                    roundRect.topLeftCornerRadius.y,
+                                    roundRect.topRightCornerRadius.x,
+                                    roundRect.topRightCornerRadius.y,
+                                )
+                            val bottomCornerRadius =
+                                maxOf(
+                                    roundRect.bottomLeftCornerRadius.x,
+                                    roundRect.bottomLeftCornerRadius.y,
+                                    roundRect.bottomRightCornerRadius.x,
+                                    roundRect.bottomRightCornerRadius.y,
+                                )
+
+                            topCornerRadius to bottomCornerRadius
+                        }
+                        else ->
+                            error(
+                                "ExpandableState only supports (rounded) rectangles at the " +
+                                    "moment."
+                            )
+                    }
+
+                val rootLocation = rootLocationOnScreen()
+                return LaunchAnimator.State(
+                    top = rootLocation.y.roundToInt(),
+                    bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
+                    left = rootLocation.x.roundToInt(),
+                    right = (rootLocation.x + boundsInRoot.width).roundToInt(),
+                    topCornerRadius = topCornerRadius,
+                    bottomCornerRadius = bottomCornerRadius,
+                )
+            }
+
+            private fun rootLocationOnScreen(): Offset {
+                composeViewRoot.getLocationOnScreen(rootLocationOnScreen)
+                val boundsInRoot = boundsInComposeViewRoot.value
+                val x = rootLocationOnScreen[0] + boundsInRoot.left
+                val y = rootLocationOnScreen[1] + boundsInRoot.top
+                return Offset(x, y)
+            }
+        }
+    }
+
+    /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
+    private fun activityController(): ActivityLaunchAnimator.Controller {
+        val delegate = launchController()
+        return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
+            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+            }
+
+            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                overlay.value = null
+            }
+        }
+    }
+
+    private fun dialogController(): DialogLaunchAnimator.Controller {
+        return object : DialogLaunchAnimator.Controller {
+            override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+            override val sourceIdentity: Any = this@ExpandableControllerImpl
+
+            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+                val newOverlay = viewGroup.overlay as ViewGroupOverlay
+                if (newOverlay != overlay.value) {
+                    overlay.value = newOverlay
+                }
+            }
+
+            override fun stopDrawingInOverlay() {
+                if (overlay.value != null) {
+                    overlay.value = null
+                }
+            }
+
+            override fun createLaunchController(): LaunchAnimator.Controller {
+                val delegate = launchController()
+                return object : LaunchAnimator.Controller by delegate {
+                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+                        // Make sure we don't draw this expandable when the dialog is showing.
+                        isDialogShowing.value = true
+                    }
+                }
+            }
+
+            override fun createExitController(): LaunchAnimator.Controller {
+                val delegate = launchController()
+                return object : LaunchAnimator.Controller by delegate {
+                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        isDialogShowing.value = false
+                    }
+                }
+            }
+
+            override fun shouldAnimateExit(): Boolean = isComposed.value
+
+            override fun onExitAnimationCancelled() {
+                isDialogShowing.value = false
+            }
+
+            override fun jankConfigurationBuilder(
+                cuj: Int
+            ): InteractionJankMonitor.Configuration.Builder? {
+                // TODO(b/252723237): Add support for jank monitoring when animating from a
+                // Composable.
+                return null
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 5486adb..6ec65ce 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -24,7 +24,6 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     androidprv:layout_maxWidth="@dimen/keyguard_security_width"
-    androidprv:layout_maxHeight="@dimen/keyguard_security_height"
     android:layout_gravity="center_horizontal|bottom"
     android:gravity="bottom"
     >
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fa3ed21..01c9ac1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1582,4 +1582,9 @@
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen>
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
+
+    <!-- Default device corner radius, used for assist UI -->
+    <dimen name="config_rounded_mask_size">0px</dimen>
+    <dimen name="config_rounded_mask_size_top">0px</dimen>
+    <dimen name="config_rounded_mask_size_bottom">0px</dimen>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 2111df5..647dd47 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -27,6 +27,8 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -233,17 +235,14 @@
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isLocked;
 
+    public Point positionInParent;
+
+    public Rect appBounds;
+
     // Last snapshot data, only used for recent tasks
     public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
             new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
 
-    /**
-     * Indicates that this task for the desktop tile in recents.
-     *
-     * Used when desktop mode feature is enabled.
-     */
-    public boolean desktopTile;
-
     public Task() {
         // Do nothing
     }
@@ -274,7 +273,8 @@
         this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
                 other.isLocked, other.taskDescription, other.topActivity);
         lastSnapshotData.set(other.lastSnapshotData);
-        desktopTile = other.desktopTile;
+        positionInParent = other.positionInParent;
+        appBounds = other.appBounds;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 00f1c01..207f344 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -15,6 +15,12 @@
  */
 package com.android.keyguard;
 
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+
 import android.annotation.Nullable;
 import android.app.admin.IKeyguardCallback;
 import android.app.admin.IKeyguardClient;
@@ -30,7 +36,10 @@
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
-import android.view.ViewGroup;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -49,7 +58,7 @@
     private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Context mContext;
-    private final ViewGroup mParent;
+    private final ConstraintLayout mParent;
     private AdminSecurityView mView;
     private Handler mHandler;
     private IKeyguardClient mClient;
@@ -156,6 +165,7 @@
         mUpdateMonitor = updateMonitor;
         mKeyguardCallback = callback;
         mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
+        mView.setId(View.generateViewId());
     }
 
     /**
@@ -167,6 +177,15 @@
         }
         if (!mView.isAttachedToWindow()) {
             mParent.addView(mView);
+            ConstraintSet constraintSet = new ConstraintSet();
+            constraintSet.clone(mParent);
+            constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP);
+            constraintSet.connect(mView.getId(), START, PARENT_ID, START);
+            constraintSet.connect(mView.getId(), END, PARENT_ID, END);
+            constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM);
+            constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+            constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
+            constraintSet.applyTo(mParent);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 353c369..c34db15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,15 +21,23 @@
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
@@ -44,12 +52,12 @@
 import android.graphics.drawable.LayerDrawable;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.transition.TransitionManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.GestureDetector;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -59,8 +67,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.AdapterView;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -68,8 +74,9 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
 
@@ -92,9 +99,9 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 
-public class KeyguardSecurityContainer extends FrameLayout {
+/** Determines how the bouncer is displayed to the user. */
+public class KeyguardSecurityContainer extends ConstraintLayout {
     static final int USER_TYPE_PRIMARY = 1;
     static final int USER_TYPE_WORK_PROFILE = 2;
     static final int USER_TYPE_SECONDARY_USER = 3;
@@ -125,15 +132,6 @@
     // How much to scale the default slop by, to avoid accidental drags.
     private static final float SLOP_SCALE = 4f;
 
-    private static final long IME_DISAPPEAR_DURATION_MS = 125;
-
-    // The duration of the animation to switch security sides.
-    private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
-
-    // How much of the switch sides animation should be dedicated to fading the security out. The
-    // remainder will fade it back in again.
-    private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
-
     @VisibleForTesting
     KeyguardSecurityViewFlipper mSecurityViewFlipper;
     private GlobalSettings mGlobalSettings;
@@ -649,47 +647,8 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int maxHeight = 0;
-        int maxWidth = 0;
-        int childState = 0;
-
-        for (int i = 0; i < getChildCount(); i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() != GONE) {
-                int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec);
-                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-
-                // When using EXACTLY spec, measure will use the layout width if > 0. Set before
-                // measuring the child
-                lp.width = MeasureSpec.getSize(updatedWidthMeasureSpec);
-                measureChildWithMargins(view, updatedWidthMeasureSpec, 0,
-                        heightMeasureSpec, 0);
-
-                maxWidth = Math.max(maxWidth,
-                        view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-                maxHeight = Math.max(maxHeight,
-                        view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
-                childState = combineMeasuredStates(childState, view.getMeasuredState());
-            }
-        }
-
-        maxWidth += getPaddingLeft() + getPaddingRight();
-        maxHeight += getPaddingTop() + getPaddingBottom();
-
-        // Check against our minimum height and width
-        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
-        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-
-        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-                resolveSizeAndState(maxHeight, heightMeasureSpec,
-                        childState << MEASURED_HEIGHT_STATE_SHIFT));
-    }
-
-    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-
         int width = right - left;
         if (changed && mWidth != width) {
             mWidth = width;
@@ -761,7 +720,7 @@
      * Enscapsulates the differences between bouncer modes for the container.
      */
     interface ViewMode {
-        default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+        default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
                 @NonNull UserSwitcherController userSwitcherController) {};
@@ -787,11 +746,6 @@
         /** On notif tap, this animation will run */
         default void startAppearAnimation(SecurityMode securityMode) {};
 
-        /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
-        default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
-            return parentWidthMeasureSpec;
-        }
-
         /** Called when we are setting a new ViewMode */
         default void onDestroy() {};
     }
@@ -801,13 +755,12 @@
      * screen devices
      */
     abstract static class SidedSecurityMode implements ViewMode {
-        @Nullable private ValueAnimator mRunningSecurityShiftAnimator;
         private KeyguardSecurityViewFlipper mViewFlipper;
-        private ViewGroup mView;
+        private ConstraintLayout mView;
         private GlobalSettings mGlobalSettings;
         private int mDefaultSideSetting;
 
-        public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
+        public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper,
                 GlobalSettings globalSettings, boolean leftAlignedByDefault) {
             mView = v;
             mViewFlipper = viewFlipper;
@@ -850,127 +803,6 @@
 
         protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
 
-        protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
-            translateSecurityViewLocation(leftAlign, animate, i -> {});
-        }
-
-        /**
-         * Moves the inner security view to the correct location with animation. This is triggered
-         * when the user double taps on the side of the screen that is not currently occupied by
-         * the security view.
-         */
-        protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
-                Consumer<Float> securityAlphaListener) {
-            if (mRunningSecurityShiftAnimator != null) {
-                mRunningSecurityShiftAnimator.cancel();
-                mRunningSecurityShiftAnimator = null;
-            }
-
-            int targetTranslation = leftAlign
-                    ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth();
-
-            if (animate) {
-                // This animation is a bit fun to implement. The bouncer needs to move, and fade
-                // in/out at the same time. The issue is, the bouncer should only move a short
-                // amount (120dp or so), but obviously needs to go from one side of the screen to
-                // the other. This needs a pretty custom animation.
-                //
-                // This works as follows. It uses a ValueAnimation to simply drive the animation
-                // progress. This animator is responsible for both the translation of the bouncer,
-                // and the current fade. It will fade the bouncer out while also moving it along the
-                // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
-                // bouncer closer to its destination, then fade it back in again. The effect is that
-                // the bouncer will move from 0 -> X while fading out, then
-                // (destination - X) -> destination while fading back in again.
-                // TODO(b/208250221): Make this animation properly abortable.
-                Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
-                        mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
-                Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
-                Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
-
-                mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
-                mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
-                mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
-
-                int initialTranslation = (int) mViewFlipper.getTranslationX();
-                int totalTranslation = (int) mView.getResources().getDimension(
-                        R.dimen.security_shift_animation_translation);
-
-                final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
-                        && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
-                if (shouldRestoreLayerType) {
-                    mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
-                }
-
-                float initialAlpha = mViewFlipper.getAlpha();
-
-                mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mRunningSecurityShiftAnimator = null;
-                    }
-                });
-                mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
-                    float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION;
-                    boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
-
-                    int currentTranslation = (int) (positionInterpolator.getInterpolation(
-                            animation.getAnimatedFraction()) * totalTranslation);
-                    int translationRemaining = totalTranslation - currentTranslation;
-
-                    // Flip the sign if we're going from right to left.
-                    if (leftAlign) {
-                        currentTranslation = -currentTranslation;
-                        translationRemaining = -translationRemaining;
-                    }
-
-                    float opacity;
-                    if (isFadingOut) {
-                        // The bouncer fades out over the first X%.
-                        float fadeOutFraction = MathUtils.constrainedMap(
-                                /* rangeMin= */1.0f,
-                                /* rangeMax= */0.0f,
-                                /* valueMin= */0.0f,
-                                /* valueMax= */switchPoint,
-                                animation.getAnimatedFraction());
-                        opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-
-                        // When fading out, the alpha needs to start from the initial opacity of the
-                        // view flipper, otherwise we get a weird bit of jank as it ramps back to
-                        // 100%.
-                        mViewFlipper.setAlpha(opacity * initialAlpha);
-
-                        // Animate away from the source.
-                        mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
-                    } else {
-                        // And in again over the remaining (100-X)%.
-                        float fadeInFraction = MathUtils.constrainedMap(
-                                /* rangeMin= */0.0f,
-                                /* rangeMax= */1.0f,
-                                /* valueMin= */switchPoint,
-                                /* valueMax= */1.0f,
-                                animation.getAnimatedFraction());
-
-                        opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
-                        mViewFlipper.setAlpha(opacity);
-
-                        // Fading back in, animate towards the destination.
-                        mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
-                    }
-                    securityAlphaListener.accept(opacity);
-
-                    if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
-                        mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
-                    }
-                });
-
-                mRunningSecurityShiftAnimator.start();
-            } else {
-                mViewFlipper.setTranslationX(targetTranslation);
-            }
-        }
-
-
         boolean isLeftAligned() {
             return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
                     mDefaultSideSetting)
@@ -989,11 +821,11 @@
      * Default bouncer is centered within the space
      */
     static class DefaultViewMode implements ViewMode {
-        private ViewGroup mView;
+        private ConstraintLayout mView;
         private KeyguardSecurityViewFlipper mViewFlipper;
 
         @Override
-        public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+        public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
                 @NonNull UserSwitcherController userSwitcherController) {
@@ -1005,11 +837,14 @@
         }
 
         private void updateSecurityViewGroup() {
-            FrameLayout.LayoutParams lp =
-                    (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
-            lp.gravity = Gravity.CENTER_HORIZONTAL;
-            mViewFlipper.setLayoutParams(lp);
-            mViewFlipper.setTranslationX(0);
+            ConstraintSet constraintSet = new ConstraintSet();
+            constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START);
+            constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END);
+            constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+            constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+            constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+            constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+            constraintSet.applyTo(mView);
         }
     }
 
@@ -1018,7 +853,7 @@
      * a user switcher, in both portrait and landscape modes.
      */
     static class UserSwitcherViewMode extends SidedSecurityMode {
-        private ViewGroup mView;
+        private ConstraintLayout mView;
         private ViewGroup mUserSwitcherViewGroup;
         private KeyguardSecurityViewFlipper mViewFlipper;
         private TextView mUserSwitcher;
@@ -1029,11 +864,8 @@
         private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
                 this::setupUserSwitcher;
 
-        private float mAnimationLastAlpha = 1f;
-        private boolean mAnimationWaitsToShift = true;
-
         @Override
-        public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+        public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
                 @NonNull UserSwitcherController userSwitcherController) {
@@ -1240,88 +1072,55 @@
             });
         }
 
-        /**
-         * Each view will get half the width. Yes, it would be easier to use something other than
-         * FrameLayout but it was too disruptive to downstream projects to change.
-         */
-        @Override
-        public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
-            return MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
-                    MeasureSpec.EXACTLY);
-        }
-
         @Override
         public void updateSecurityViewLocation() {
             updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
         }
 
         public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
-            setYTranslation();
-            setGravity();
-            setXTranslation(leftAlign, animate);
-        }
-
-        private void setXTranslation(boolean leftAlign, boolean animate) {
-            if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
-                mUserSwitcherViewGroup.setTranslationX(0);
-                mViewFlipper.setTranslationX(0);
-            } else {
-                int switcherTargetTranslation = leftAlign
-                        ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
-                if (animate) {
-                    mAnimationWaitsToShift = true;
-                    mAnimationLastAlpha = 1f;
-                    translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
-                        // During the animation security view fades out - alpha goes from 1 to
-                        // (almost) 0 - and then fades in - alpha grows back to 1.
-                        // If new alpha is bigger than previous one it means we're at inflection
-                        // point and alpha is zero or almost zero. That's when we want to do
-                        // translation of user switcher, so that it's not visible to the user.
-                        boolean fullyFadeOut = securityAlpha == 0.0f
-                                || securityAlpha > mAnimationLastAlpha;
-                        if (fullyFadeOut && mAnimationWaitsToShift) {
-                            mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
-                            mAnimationWaitsToShift = false;
-                        }
-                        mUserSwitcherViewGroup.setAlpha(securityAlpha);
-                        mAnimationLastAlpha = securityAlpha;
-                    });
-                } else {
-                    translateSecurityViewLocation(leftAlign, animate);
-                    mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
-                }
+            if (animate) {
+                TransitionManager.beginDelayedTransition(mView,
+                        new KeyguardSecurityViewTransition());
             }
-
-        }
-
-        private void setGravity() {
-            if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
-                updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
-                updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
-            } else {
-                // horizontal gravity is the same because we translate these views anyway
-                updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
-                updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
-            }
-        }
-
-        private void setYTranslation() {
             int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
             if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
-                mUserSwitcherViewGroup.setTranslationY(yTrans);
+                ConstraintSet constraintSet = new ConstraintSet();
+                constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
+                constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+                constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+                constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
+                constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
+                constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+                constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+                constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+                constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
+                constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+                constraintSet.applyTo(mView);
             } else {
-                // Attempt to reposition a bit higher to make up for this frame being a bit lower
-                // on the device
-                mUserSwitcherViewGroup.setTranslationY(-yTrans);
-                mViewFlipper.setTranslationY(0);
-            }
-        }
+                int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
+                int rightElement =
+                        leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
 
-        private void updateViewGravity(View v, int gravity) {
-            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
-            lp.gravity = gravity;
-            v.setLayoutParams(lp);
+                ConstraintSet constraintSet = new ConstraintSet();
+                constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
+                constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
+                constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
+                constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
+                constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
+                constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
+                        yTrans);
+                constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+                constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+                constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
+                constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
+                constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(),
+                        MATCH_CONSTRAINT);
+                constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(),
+                        MATCH_CONSTRAINT);
+                constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
+                constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
+                constraintSet.applyTo(mView);
+            }
         }
     }
 
@@ -1330,11 +1129,11 @@
      * between alternate sides of the display.
      */
     static class OneHandedViewMode extends SidedSecurityMode {
-        private ViewGroup mView;
+        private ConstraintLayout mView;
         private KeyguardSecurityViewFlipper mViewFlipper;
 
         @Override
-        public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+        public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
                 @NonNull UserSwitcherController userSwitcherController) {
@@ -1342,28 +1141,10 @@
             mView = v;
             mViewFlipper = viewFlipper;
 
-            updateSecurityViewGravity();
             updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
         }
 
         /**
-         * One-handed mode contains the child to half of the available space.
-         */
-        @Override
-        public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
-            return MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
-                    MeasureSpec.EXACTLY);
-        }
-
-        private void updateSecurityViewGravity() {
-            FrameLayout.LayoutParams lp =
-                    (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
-            lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
-            mViewFlipper.setLayoutParams(lp);
-        }
-
-        /**
          * Moves the bouncer to align with a tap (most likely in the shade), so the bouncer
          * appears on the same side as a touch.
          */
@@ -1380,7 +1161,20 @@
         }
 
         protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
-            translateSecurityViewLocation(leftAlign, animate);
+            if (animate) {
+                TransitionManager.beginDelayedTransition(mView,
+                        new KeyguardSecurityViewTransition());
+            }
+            ConstraintSet constraintSet = new ConstraintSet();
+            if (leftAlign) {
+                constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT);
+            } else {
+                constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT);
+            }
+            constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
+            constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+            constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
+            constraintSet.applyTo(mView);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
new file mode 100644
index 0000000..9eb2c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.MathUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import com.android.internal.R.interpolator.fast_out_extra_slow_in
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+
+/** Animates constraint layout changes for the security view. */
+class KeyguardSecurityViewTransition : Transition() {
+
+    companion object {
+        const val PROP_BOUNDS = "securityViewLocation:bounds"
+
+        // The duration of the animation to switch security sides.
+        const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L
+
+        // How much of the switch sides animation should be dedicated to fading the security out.
+        // The remainder will fade it back in again.
+        const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f
+    }
+
+    private fun captureValues(values: TransitionValues) {
+        val boundsRect = Rect()
+        boundsRect.left = values.view.left
+        boundsRect.top = values.view.top
+        boundsRect.right = values.view.right
+        boundsRect.bottom = values.view.bottom
+        values.values[PROP_BOUNDS] = boundsRect
+    }
+
+    override fun getTransitionProperties(): Array<String>? {
+        return arrayOf(PROP_BOUNDS)
+    }
+
+    override fun captureEndValues(transitionValues: TransitionValues?) {
+        transitionValues?.let { captureValues(it) }
+    }
+
+    override fun captureStartValues(transitionValues: TransitionValues?) {
+        transitionValues?.let { captureValues(it) }
+    }
+
+    override fun createAnimator(
+        sceneRoot: ViewGroup?,
+        startValues: TransitionValues?,
+        endValues: TransitionValues?
+    ): Animator? {
+        if (sceneRoot == null || startValues == null || endValues == null) {
+            return null
+        }
+
+        // This animation is a bit fun to implement. The bouncer needs to move, and fade
+        // in/out at the same time. The issue is, the bouncer should only move a short
+        // amount (120dp or so), but obviously needs to go from one side of the screen to
+        // the other. This needs a pretty custom animation.
+        //
+        // This works as follows. It uses a ValueAnimation to simply drive the animation
+        // progress. This animator is responsible for both the translation of the bouncer,
+        // and the current fade. It will fade the bouncer out while also moving it along the
+        // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
+        // bouncer closer to its destination, then fade it back in again. The effect is that
+        // the bouncer will move from 0 -> X while fading out, then
+        // (destination - X) -> destination while fading back in again.
+        // TODO(b/208250221): Make this animation properly abortable.
+        val positionInterpolator =
+            AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in)
+        val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN
+        val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN
+        var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
+        runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS
+        runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR
+        val startRect = startValues.values[PROP_BOUNDS] as Rect
+        val endRect = endValues.values[PROP_BOUNDS] as Rect
+        val v = startValues.view
+        val totalTranslation: Int =
+            sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt()
+        val shouldRestoreLayerType =
+            (v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE)
+        if (shouldRestoreLayerType) {
+            v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null)
+        }
+        val initialAlpha: Float = v.alpha
+        runningSecurityShiftAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    runningSecurityShiftAnimator = null
+                }
+            }
+        )
+
+        var finishedFadingOutNonSecurityView = false
+
+        runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
+            val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
+            val isFadingOut = animation.animatedFraction < switchPoint
+            val opacity: Float
+            var currentTranslation =
+                (positionInterpolator.getInterpolation(animation.animatedFraction) *
+                        totalTranslation)
+                    .toInt()
+            var translationRemaining = totalTranslation - currentTranslation
+            val leftAlign = endRect.left < startRect.left
+            if (leftAlign) {
+                currentTranslation = -currentTranslation
+                translationRemaining = -translationRemaining
+            }
+
+            if (isFadingOut) {
+                // The bouncer fades out over the first X%.
+                val fadeOutFraction =
+                    MathUtils.constrainedMap(
+                        /* rangeMin= */ 1.0f,
+                        /* rangeMax= */ 0.0f,
+                        /* valueMin= */ 0.0f,
+                        /* valueMax= */ switchPoint,
+                        animation.animatedFraction
+                    )
+                opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction)
+
+                // When fading out, the alpha needs to start from the initial opacity of the
+                // view flipper, otherwise we get a weird bit of jank as it ramps back to
+                // 100%.
+                v.alpha = opacity * initialAlpha
+                if (v is KeyguardSecurityViewFlipper) {
+                    v.setLeftTopRightBottom(
+                        startRect.left + currentTranslation,
+                        startRect.top,
+                        startRect.right + currentTranslation,
+                        startRect.bottom
+                    )
+                }
+            } else {
+                // And in again over the remaining (100-X)%.
+                val fadeInFraction =
+                    MathUtils.constrainedMap(
+                        /* rangeMin= */ 0.0f,
+                        /* rangeMax= */ 1.0f,
+                        /* valueMin= */ switchPoint,
+                        /* valueMax= */ 1.0f,
+                        animation.animatedFraction
+                    )
+                opacity = fadeInInterpolator.getInterpolation(fadeInFraction)
+                v.alpha = opacity
+
+                // Fading back in, animate towards the destination.
+                if (v is KeyguardSecurityViewFlipper) {
+                    v.setLeftTopRightBottom(
+                        endRect.left - translationRemaining,
+                        endRect.top,
+                        endRect.right - translationRemaining,
+                        endRect.bottom
+                    )
+                }
+            }
+            if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
+                v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
+            }
+
+            // For views that are not the security view flipper, we do not want to apply
+            // an x translation animation. Instead, we want to fade out, move to final position and
+            // then fade in.
+            if (v !is KeyguardSecurityViewFlipper) {
+                // Opacity goes close to 0 but does not fully get to 0.
+                if (opacity - 0.001f < 0f) {
+                    v.setLeftTopRightBottom(
+                        endRect.left,
+                        endRect.top,
+                        endRect.right,
+                        endRect.bottom
+                    )
+                    finishedFadingOutNonSecurityView = true
+                } else if (!finishedFadingOutNonSecurityView) {
+                    v.setLeftTopRightBottom(
+                        startRect.left,
+                        startRect.top,
+                        startRect.right,
+                        startRect.bottom
+                    )
+                }
+            }
+        }
+        runningSecurityShiftAnimator.start()
+        return runningSecurityShiftAnimator
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 761aa7e9..80120a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2333,7 +2333,7 @@
      */
     public void requestFaceAuth(boolean userInitiatedRequest,
             @FaceAuthApiRequestReason String reason) {
-        mLogger.logFaceAuthRequested(userInitiatedRequest);
+        mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
     }
 
@@ -3164,14 +3164,7 @@
      * Whether the keyguard is showing and not occluded.
      */
     public boolean isKeyguardVisible() {
-        return isKeyguardShowing() && !mKeyguardOccluded;
-    }
-
-    /**
-     * Whether the keyguard is showing. It may still be occluded and not visible.
-     */
-    public boolean isKeyguardShowing() {
-        return mKeyguardShowing;
+        return mKeyguardShowing && !mKeyguardOccluded;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 3ea8826..90f0446 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -94,11 +94,6 @@
     void setOccluded(boolean occluded, boolean animate);
 
     /**
-     * @return Whether the keyguard is showing
-     */
-    boolean isShowing();
-
-    /**
      * Dismisses the keyguard by going to the next screen or making it gone.
      */
     void dismissAndCollapse();
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 54cec71..2eee957 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -108,10 +108,11 @@
                 }, { "Face help received, msgId: $int1 msg: $str1" })
     }
 
-    fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
-        logBuffer.log(TAG, DEBUG,
-                { bool1 = userInitiatedRequest },
-                { "requestFaceAuth() userInitiated=$bool1" })
+    fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+        logBuffer.log(TAG, DEBUG, {
+            bool1 = userInitiatedRequest
+            str1 = reason
+        }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
     }
 
     fun logFaceAuthSuccess(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 109be40..37829f2 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -3,6 +3,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -55,7 +56,11 @@
         } else {
             PackageManager.COMPONENT_ENABLED_STATE_DISABLED
         }
-        packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+        try {
+            packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
+        } catch (e: IllegalArgumentException) {
+            Log.w("ChooserSelector", "Unable to set IntentResolver enabled=" + enabled, e)
+        }
     }
 
     suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
index 33e6ca4..9b441ad 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
@@ -21,6 +21,8 @@
 import android.view.Display;
 import android.view.Surface;
 
+import com.android.systemui.R;
+
 /**
  * Utility class for determining screen and corner dimensions.
  */
@@ -82,17 +84,13 @@
      * where the curve ends), in pixels.
      */
     public static int getCornerRadiusBottom(Context context) {
-        int radius = 0;
-
-        int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_bottom",
-                "dimen", "com.android.systemui");
-        if (resourceId > 0) {
-            radius = context.getResources().getDimensionPixelSize(resourceId);
-        }
+        int radius = context.getResources().getDimensionPixelSize(
+                R.dimen.config_rounded_mask_size_bottom);
 
         if (radius == 0) {
             radius = getCornerRadiusDefault(context);
         }
+
         return radius;
     }
 
@@ -101,28 +99,17 @@
      * the curve ends), in pixels.
      */
     public static int getCornerRadiusTop(Context context) {
-        int radius = 0;
-
-        int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_top",
-                "dimen", "com.android.systemui");
-        if (resourceId > 0) {
-            radius = context.getResources().getDimensionPixelSize(resourceId);
-        }
+        int radius = context.getResources().getDimensionPixelSize(
+                R.dimen.config_rounded_mask_size_top);
 
         if (radius == 0) {
             radius = getCornerRadiusDefault(context);
         }
+
         return radius;
     }
 
     private static int getCornerRadiusDefault(Context context) {
-        int radius = 0;
-
-        int resourceId = context.getResources().getIdentifier("config_rounded_mask_size",
-                "dimen", "com.android.systemui");
-        if (resourceId > 0) {
-            radius = context.getResources().getDimensionPixelSize(resourceId);
-        }
-        return radius;
+        return context.getResources().getDimensionPixelSize(R.dimen.config_rounded_mask_size);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2578df3..0f5a99c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -245,7 +245,7 @@
                     mAcquiredReceived = true;
                     final UdfpsView view = mOverlay.getOverlayView();
                     if (view != null) {
-                        view.unconfigureDisplay();
+                        unconfigureDisplay(view);
                     }
                     if (acquiredGood) {
                         mOverlay.onAcquiredGood();
@@ -737,6 +737,19 @@
 
         mOverlay = null;
         mOrientationListener.disable();
+
+    }
+
+    private void unconfigureDisplay(@NonNull UdfpsView view) {
+        if (view.isDisplayConfigured()) {
+            view.unconfigureDisplay();
+
+            if (mCancelAodTimeoutAction != null) {
+                mCancelAodTimeoutAction.run();
+                mCancelAodTimeoutAction = null;
+            }
+            mIsAodInterruptActive = false;
+        }
     }
 
     /**
@@ -812,12 +825,12 @@
      * sensors, this can result in illumination persisting for longer than necessary.
      */
     void onCancelUdfps() {
-        if (mOverlay != null && mOverlay.getOverlayView() != null) {
-            onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
-        }
         if (!mIsAodInterruptActive) {
             return;
         }
+        if (mOverlay != null && mOverlay.getOverlayView() != null) {
+            onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
+        }
         if (mCancelAodTimeoutAction != null) {
             mCancelAodTimeoutAction.run();
             mCancelAodTimeoutAction = null;
@@ -911,15 +924,8 @@
             }
         }
         mOnFingerDown = false;
-        if (view.isDisplayConfigured()) {
-            view.unconfigureDisplay();
-        }
+        unconfigureDisplay(view);
 
-        if (mCancelAodTimeoutAction != null) {
-            mCancelAodTimeoutAction.run();
-            mCancelAodTimeoutAction = null;
-        }
-        mIsAodInterruptActive = false;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 934aedf..4d7f89d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -219,7 +219,7 @@
                 mView.animateInUdfpsBouncer(null);
             }
 
-            if (mKeyguardViewManager.isOccluded()) {
+            if (mKeyguardStateController.isOccluded()) {
                 mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 0d06c51..c2dffe8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -27,9 +27,6 @@
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
-import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
 import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.FoldStateLogger;
@@ -133,9 +130,6 @@
         });
         getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
         // No init method needed, just needs to be gotten so that it's created.
-        getMediaTttChipControllerSender();
-        getMediaTttChipControllerReceiver();
-        getMediaTttCommandLineHelper();
         getMediaMuteAwaitConnectionCli();
         getNearbyMediaDevicesManager();
         getUnfoldLatencyTracker().init();
@@ -206,15 +200,6 @@
     Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
 
     /** */
-    Optional<MediaTttChipControllerSender> getMediaTttChipControllerSender();
-
-    /** */
-    Optional<MediaTttChipControllerReceiver> getMediaTttChipControllerReceiver();
-
-    /** */
-    Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
-
-    /** */
     Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8bb27a7..55eda0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -32,6 +32,9 @@
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.media.RingtonePlayer
+import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
+import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
+import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
 import com.android.systemui.power.PowerUI
 import com.android.systemui.recents.Recents
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
@@ -213,4 +216,26 @@
     @IntoMap
     @ClassKey(KeyguardLiftController::class)
     abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
+
+    /** Inject into MediaTttChipControllerReceiver. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaTttChipControllerReceiver::class)
+    abstract fun bindMediaTttChipControllerReceiver(
+            sysui: MediaTttChipControllerReceiver
+    ): CoreStartable
+
+    /** Inject into MediaTttChipControllerSender. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaTttChipControllerSender::class)
+    abstract fun bindMediaTttChipControllerSender(
+            sysui: MediaTttChipControllerSender
+    ): CoreStartable
+
+    /** Inject into MediaTttCommandLineHelper. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaTttCommandLineHelper::class)
+    abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 696fc72..d1b7368 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -64,29 +64,26 @@
     private final Executor mExecutor;
     // A controller for the dream overlay container view (which contains both the status bar and the
     // content area).
-    private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+    private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Nullable
     private final ComponentName mLowLightDreamComponent;
     private final UiEventLogger mUiEventLogger;
+    private final WindowManager mWindowManager;
 
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
 
-    // True if the service has been destroyed.
-    private boolean mDestroyed;
+    // True if a dream has bound to the service and dream overlay service has started.
+    private boolean mStarted = false;
 
-    private final Complication.Host mHost = new Complication.Host() {
-        @Override
-        public void requestExitDream() {
-            mExecutor.execute(DreamOverlayService.this::requestExit);
-        }
-    };
+    // True if the service has been destroyed.
+    private boolean mDestroyed = false;
+
+    private final DreamOverlayComponent mDreamOverlayComponent;
 
     private final LifecycleRegistry mLifecycleRegistry;
 
-    private ViewModelStore mViewModelStore = new ViewModelStore();
-
     private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
 
     private final KeyguardUpdateMonitorCallback mKeyguardCallback =
@@ -103,7 +100,7 @@
                 }
             };
 
-    private DreamOverlayStateController mStateController;
+    private final DreamOverlayStateController mStateController;
 
     @VisibleForTesting
     public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
@@ -128,6 +125,7 @@
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
+            WindowManager windowManager,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -136,19 +134,19 @@
                     ComponentName lowLightDreamComponent) {
         mContext = context;
         mExecutor = executor;
+        mWindowManager = windowManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLowLightDreamComponent = lowLightDreamComponent;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
 
-        final DreamOverlayComponent component =
-                dreamOverlayComponentFactory.create(mViewModelStore, mHost);
-        mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+        final ViewModelStore viewModelStore = new ViewModelStore();
+        final Complication.Host host =
+                () -> mExecutor.execute(DreamOverlayService.this::requestExit);
+        mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
+        mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
         setCurrentState(Lifecycle.State.CREATED);
-        mLifecycleRegistry = component.getLifecycleRegistry();
-        mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
-        mDreamOverlayTouchMonitor.init();
     }
 
     private void setCurrentState(Lifecycle.State state) {
@@ -159,34 +157,48 @@
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
         setCurrentState(Lifecycle.State.DESTROYED);
-        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        if (mWindow != null) {
-            windowManager.removeView(mWindow.getDecorView());
-        }
-        mStateController.setOverlayActive(false);
-        mStateController.setLowLightActive(false);
+
+        resetCurrentDreamOverlay();
+
         mDestroyed = true;
         super.onDestroy();
     }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
         setCurrentState(Lifecycle.State.STARTED);
-        final ComponentName dreamComponent = getDreamComponent();
-        mStateController.setLowLightActive(
-                dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
+
         mExecutor.execute(() -> {
+            mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+
             if (mDestroyed) {
                 // The task could still be executed after the service has been destroyed. Bail if
                 // that is the case.
                 return;
             }
+
+            if (mStarted) {
+                // Reset the current dream overlay before starting a new one. This can happen
+                // when two dreams overlap (briefly, for a smoother dream transition) and both
+                // dreams are bound to the dream overlay service.
+                resetCurrentDreamOverlay();
+            }
+
+            mDreamOverlayContainerViewController =
+                    mDreamOverlayComponent.getDreamOverlayContainerViewController();
+            mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
+            mDreamOverlayTouchMonitor.init();
+
             mStateController.setShouldShowComplications(shouldShowComplications());
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
+            final ComponentName dreamComponent = getDreamComponent();
+            mStateController.setLowLightActive(
+                    dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
             mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+
+            mStarted = true;
         });
     }
 
@@ -222,8 +234,7 @@
         removeContainerViewFromParent();
         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
 
-        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+        mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
     }
 
     private void removeContainerViewFromParent() {
@@ -238,4 +249,18 @@
         Log.w(TAG, "Removing dream overlay container view parent!");
         parentView.removeView(containerView);
     }
+
+    private void resetCurrentDreamOverlay() {
+        if (mStarted && mWindow != null) {
+            mWindowManager.removeView(mWindow.getDecorView());
+        }
+
+        mStateController.setOverlayActive(false);
+        mStateController.setLowLightActive(false);
+
+        mDreamOverlayContainerViewController = null;
+        mDreamOverlayTouchMonitor = null;
+
+        mStarted = false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index fcf11ef..3dbadb0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -116,7 +116,7 @@
      * the framework APIs.
      */
     public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
-            new UnreleasedFlag(210, true);
+            new UnreleasedFlag(210);
 
     /**
      * Whether `UserSwitcherController` should use the user interactor.
@@ -127,8 +127,7 @@
      * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
      * {@code true} as it would created a cycle between controller -> interactor -> controller.
      */
-    public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
-            new UnreleasedFlag(211, false);
+    public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
 
     /***************************************/
     // 300 - power menu
@@ -300,8 +299,8 @@
     public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
     public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
 
-    // 1400 - columbus, b/242800729
-    public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+    // 1400 - columbus
+    public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
 
     // 1500 - chooser
     public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index ad8c688..c4eac1c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -662,7 +662,7 @@
     }
 
     override fun onKeyguardDismissAmountChanged() {
-        if (keyguardViewController.isShowing && !playingCannedUnlockAnimation) {
+        if (keyguardStateController.isShowing && !playingCannedUnlockAnimation) {
             showOrHideSurfaceIfDismissAmountThresholdsReached()
 
             // If the surface is visible or it's about to be, start updating its appearance to
@@ -721,7 +721,7 @@
      */
     private fun finishKeyguardExitRemoteAnimationIfReachThreshold() {
         // no-op if keyguard is not showing or animation is not enabled.
-        if (!keyguardViewController.isShowing) {
+        if (!keyguardStateController.isShowing) {
             return
         }
 
@@ -844,7 +844,7 @@
      * animation.
      */
     fun hideKeyguardViewAfterRemoteAnimation() {
-        if (keyguardViewController.isShowing) {
+        if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
             keyguardViewController.hide(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7155acf..aee70ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1875,7 +1875,7 @@
         // if the keyguard is already showing, don't bother. check flags in both files
         // to account for the hiding animation which results in a delay and discrepancy
         // between flags
-        if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) {
+        if (mShowing && mKeyguardStateController.isShowing()) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
             resetStateLocked();
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 0c5564b..28aa19e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -322,7 +322,7 @@
     @SysUISingleton
     @KeyguardUpdateMonitorLog
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
-        return factory.create("KeyguardUpdateMonitorLog", 200);
+        return factory.create("KeyguardUpdateMonitorLog", 400);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 00b0ff9..f5caefb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -22,6 +22,7 @@
 import android.media.MediaRoute2Info
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
@@ -39,14 +40,10 @@
  */
 @SysUISingleton
 class MediaTttCommandLineHelper @Inject constructor(
-    commandRegistry: CommandRegistry,
+    private val commandRegistry: CommandRegistry,
     private val context: Context,
     @Main private val mainExecutor: Executor
-) {
-    init {
-        commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
-        commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
-    }
+) : CoreStartable(context) {
 
     /** All commands for the sender device. */
     inner class SenderCommand : Command {
@@ -56,7 +53,7 @@
             val displayState: Int?
             try {
                 displayState = ChipStateSender.getSenderStateIdFromName(commandName)
-            }  catch (ex: IllegalArgumentException) {
+            } catch (ex: IllegalArgumentException) {
                 pw.println("Invalid command name $commandName")
                 return
             }
@@ -150,6 +147,11 @@
                     "<chipState> useAppIcon=[true|false]")
         }
     }
+
+    override fun start() {
+        commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
+        commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
+    }
 }
 
 @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 511c4bf..1461293 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -55,7 +55,7 @@
  */
 @SysUISingleton
 class MediaTttChipControllerReceiver @Inject constructor(
-        commandQueue: CommandQueue,
+        private val commandQueue: CommandQueue,
         context: Context,
         @MediaTttReceiverLogger logger: MediaTttLogger,
         windowManager: WindowManager,
@@ -101,10 +101,6 @@
         }
     }
 
-    init {
-        commandQueue.addCallback(commandQueueCallbacks)
-    }
-
     private fun updateMediaTapToTransferReceiverDisplay(
         @StatusBarManager.MediaTransferReceiverState displayState: Int,
         routeInfo: MediaRoute2Info,
@@ -141,6 +137,10 @@
         )
     }
 
+    override fun start() {
+        commandQueue.addCallback(commandQueueCallbacks)
+    }
+
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
         val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context, newInfo.routeInfo.clientPackageName, logger
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index a98158e..5d63145 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -48,7 +48,6 @@
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
-import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -57,7 +56,7 @@
  */
 @SysUISingleton
 open class MediaTttChipControllerSender @Inject constructor(
-        commandQueue: CommandQueue,
+        private val commandQueue: CommandQueue,
         context: Context,
         @MediaTttSenderLogger logger: MediaTttLogger,
         windowManager: WindowManager,
@@ -66,10 +65,8 @@
         configurationController: ConfigurationController,
         powerManager: PowerManager,
         private val uiEventLogger: MediaTttSenderUiEventLogger,
-        // Added Lazy<> to delay the time we create Falsing instances.
-        // And overcome performance issue, check [b/247817628] for details.
-        private val falsingManager: Lazy<FalsingManager>,
-        private val falsingCollector: Lazy<FalsingCollector>,
+        private val falsingManager: FalsingManager,
+        private val falsingCollector: FalsingCollector,
         private val viewUtil: ViewUtil,
 ) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
         context,
@@ -102,10 +99,6 @@
         }
     }
 
-    init {
-        commandQueue.addCallback(commandQueueCallbacks)
-    }
-
     private fun updateMediaTapToTransferSenderDisplay(
         @StatusBarManager.MediaTransferSenderState displayState: Int,
         routeInfo: MediaRoute2Info,
@@ -128,6 +121,10 @@
         }
     }
 
+    override fun start() {
+        commandQueue.addCallback(commandQueueCallbacks)
+    }
+
     override fun updateView(
         newInfo: ChipSenderInfo,
         currentView: ViewGroup
@@ -138,7 +135,7 @@
         parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
         parent.touchHandler = object : Gefingerpoken {
             override fun onTouchEvent(ev: MotionEvent?): Boolean {
-                falsingCollector.get().onTouchEvent(ev)
+                falsingCollector.onTouchEvent(ev)
                 return false
             }
         }
@@ -167,7 +164,7 @@
                 newInfo.routeInfo,
                 newInfo.undoCallback,
                 uiEventLogger,
-                falsingManager.get(),
+                falsingManager,
         )
         undoView.setOnClickListener(undoClickListener)
         undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index da9fefa..33021e3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -48,7 +48,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.Dumpable;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -61,6 +60,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -90,7 +90,7 @@
     private final AccessibilityManager mAccessibilityManager;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    private final KeyguardViewController mKeyguardViewController;
+    private final KeyguardStateController mKeyguardStateController;
     private final UserTracker mUserTracker;
     private final SystemActions mSystemActions;
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -125,7 +125,7 @@
             OverviewProxyService overviewProxyService,
             Lazy<AssistManager> assistManagerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            KeyguardViewController keyguardViewController,
+            KeyguardStateController keyguardStateController,
             NavigationModeController navigationModeController,
             UserTracker userTracker,
             DumpManager dumpManager) {
@@ -134,7 +134,7 @@
         mAccessibilityManager = accessibilityManager;
         mAssistManagerLazy = assistManagerLazy;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
-        mKeyguardViewController = keyguardViewController;
+        mKeyguardStateController = keyguardStateController;
         mUserTracker = userTracker;
         mSystemActions = systemActions;
         accessibilityManager.addAccessibilityServicesStateChangeListener(this);
@@ -326,7 +326,7 @@
             shadeWindowView =
                     mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
         }
-        boolean isKeyguardShowing = mKeyguardViewController.isShowing();
+        boolean isKeyguardShowing = mKeyguardStateController.isShowing();
         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
                 && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
         return imeVisibleOnShade
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c337dea..6d76c17 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4287,7 +4287,7 @@
                 }
 
                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
-                        && mStatusBarKeyguardViewManager.isShowing()) {
+                        && mKeyguardStateController.isShowing()) {
                     mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
                 }
 
@@ -4405,7 +4405,7 @@
         }
         mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
                         isFullyExpanded() && !isInSettings())
-                .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
+                .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isFullyExpanded() && isInSettings())
                 .commitUpdate(mDisplayId);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 7dc9dc7..d71fbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -1,3 +1,6 @@
+justinweir@google.com
+syeonlee@google.com
+
 per-file *Notification* = set noparent
 per-file *Notification* = file:../statusbar/notification/OWNERS
 
@@ -11,4 +14,4 @@
 per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
 
 per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
\ No newline at end of file
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index 1be4c04..b5c7ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.annotation.Nullable;
 import android.util.ArraySet;
 
 import androidx.annotation.VisibleForTesting;
@@ -25,7 +24,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -43,7 +41,6 @@
 
     private boolean mLastDynamicUnlocked;
     private boolean mCacheInvalid;
-    @Nullable private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Inject
     DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
@@ -100,7 +97,7 @@
      * contents aren't revealed yet?
      */
     public boolean isInLockedDownShade() {
-        if (!isStatusBarKeyguardShowing() || !mKeyguardStateController.isMethodSecure()) {
+        if (!mKeyguardStateController.isShowing() || !mKeyguardStateController.isMethodSecure()) {
             return false;
         }
         int state = mStateController.getState();
@@ -113,16 +110,7 @@
         return true;
     }
 
-    private boolean isStatusBarKeyguardShowing() {
-        return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isShowing();
-    }
-
-    public void setStatusBarKeyguardViewManager(
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-    }
-
     public interface Listener {
         void onDynamicPrivacyChanged();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index a7719d3..e71d80c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -18,6 +18,8 @@
 
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
 import android.os.SystemClock;
 import android.service.notification.NotificationStats;
 
@@ -70,6 +72,8 @@
             dismissalSurface = NotificationStats.DISMISSAL_PEEK;
         } else if (mStatusBarStateController.isDozing()) {
             dismissalSurface = NotificationStats.DISMISSAL_AOD;
+        } else if (mStatusBarStateController.getState() == KEYGUARD) {
+            dismissalSurface = NotificationStats.DISMISSAL_LOCKSCREEN;
         }
         return new DismissedByUserStats(
                 dismissalSurface,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 3292a8f..6cf4bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -37,6 +37,19 @@
     boolean shouldHeadsUp(NotificationEntry entry);
 
     /**
+     * Returns the value of whether this entry should peek (from shouldHeadsUp(entry)), but only
+     * optionally logs the status.
+     *
+     * This method should be used in cases where the caller needs to check whether a notification
+     * qualifies for a heads up, but is not necessarily guaranteed to make the heads-up happen.
+     *
+     * @param entry the entry to check
+     * @param log whether or not to log the results of this check
+     * @return true if the entry should heads up, false otherwise
+     */
+    boolean checkHeadsUp(NotificationEntry entry, boolean log);
+
+    /**
      * Whether the notification should appear as a bubble with a fly-out on top of the screen.
      *
      * @param entry the entry to check
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 558fd62..c5a6921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -137,11 +137,11 @@
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
 
-        if (!canAlertCommon(entry)) {
+        if (!canAlertCommon(entry, true)) {
             return false;
         }
 
-        if (!canAlertAwakeCommon(entry)) {
+        if (!canAlertAwakeCommon(entry, true)) {
             return false;
         }
 
@@ -163,10 +163,15 @@
 
     @Override
     public boolean shouldHeadsUp(NotificationEntry entry) {
+        return checkHeadsUp(entry, true);
+    }
+
+    @Override
+    public boolean checkHeadsUp(NotificationEntry entry, boolean log) {
         if (mStatusBarStateController.isDozing()) {
-            return shouldHeadsUpWhenDozing(entry);
+            return shouldHeadsUpWhenDozing(entry, log);
         } else {
-            return shouldHeadsUpWhenAwake(entry);
+            return shouldHeadsUpWhenAwake(entry, log);
         }
     }
 
@@ -263,61 +268,61 @@
         }
     }
 
-    private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
+    private boolean shouldHeadsUpWhenAwake(NotificationEntry entry, boolean log) {
         StatusBarNotification sbn = entry.getSbn();
 
         if (!mUseHeadsUp) {
-            mLogger.logNoHeadsUpFeatureDisabled();
+            if (log) mLogger.logNoHeadsUpFeatureDisabled();
             return false;
         }
 
-        if (!canAlertCommon(entry)) {
+        if (!canAlertCommon(entry, log)) {
             return false;
         }
 
-        if (!canAlertHeadsUpCommon(entry)) {
+        if (!canAlertHeadsUpCommon(entry, log)) {
             return false;
         }
 
-        if (!canAlertAwakeCommon(entry)) {
+        if (!canAlertAwakeCommon(entry, log)) {
             return false;
         }
 
         if (isSnoozedPackage(sbn)) {
-            mLogger.logNoHeadsUpPackageSnoozed(entry);
+            if (log) mLogger.logNoHeadsUpPackageSnoozed(entry);
             return false;
         }
 
         boolean inShade = mStatusBarStateController.getState() == SHADE;
         if (entry.isBubble() && inShade) {
-            mLogger.logNoHeadsUpAlreadyBubbled(entry);
+            if (log) mLogger.logNoHeadsUpAlreadyBubbled(entry);
             return false;
         }
 
         if (entry.shouldSuppressPeek()) {
-            mLogger.logNoHeadsUpSuppressedByDnd(entry);
+            if (log) mLogger.logNoHeadsUpSuppressedByDnd(entry);
             return false;
         }
 
         if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-            mLogger.logNoHeadsUpNotImportant(entry);
+            if (log) mLogger.logNoHeadsUpNotImportant(entry);
             return false;
         }
 
         boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
 
         if (!inUse) {
-            mLogger.logNoHeadsUpNotInUse(entry);
+            if (log) mLogger.logNoHeadsUpNotInUse(entry);
             return false;
         }
 
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
-                mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
+                if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
                 return false;
             }
         }
-        mLogger.logHeadsUp(entry);
+        if (log) mLogger.logHeadsUp(entry);
         return true;
     }
 
@@ -328,37 +333,37 @@
      * @param entry the entry to check
      * @return true if the entry should ambient pulse, false otherwise
      */
-    private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
+    private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) {
         if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
-            mLogger.logNoPulsingSettingDisabled(entry);
+            if (log) mLogger.logNoPulsingSettingDisabled(entry);
             return false;
         }
 
         if (mBatteryController.isAodPowerSave()) {
-            mLogger.logNoPulsingBatteryDisabled(entry);
+            if (log) mLogger.logNoPulsingBatteryDisabled(entry);
             return false;
         }
 
-        if (!canAlertCommon(entry)) {
-            mLogger.logNoPulsingNoAlert(entry);
+        if (!canAlertCommon(entry, log)) {
+            if (log) mLogger.logNoPulsingNoAlert(entry);
             return false;
         }
 
-        if (!canAlertHeadsUpCommon(entry)) {
-            mLogger.logNoPulsingNoAlert(entry);
+        if (!canAlertHeadsUpCommon(entry, log)) {
+            if (log) mLogger.logNoPulsingNoAlert(entry);
             return false;
         }
 
         if (entry.shouldSuppressAmbient()) {
-            mLogger.logNoPulsingNoAmbientEffect(entry);
+            if (log) mLogger.logNoPulsingNoAmbientEffect(entry);
             return false;
         }
 
         if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
-            mLogger.logNoPulsingNotImportant(entry);
+            if (log) mLogger.logNoPulsingNotImportant(entry);
             return false;
         }
-        mLogger.logPulsing(entry);
+        if (log) mLogger.logPulsing(entry);
         return true;
     }
 
@@ -366,18 +371,22 @@
      * Common checks between regular & AOD heads up and bubbles.
      *
      * @param entry the entry to check
+     * @param log whether or not to log the results of these checks
      * @return true if these checks pass, false if the notification should not alert
      */
-    private boolean canAlertCommon(NotificationEntry entry) {
+    private boolean canAlertCommon(NotificationEntry entry, boolean log) {
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressInterruptions(entry)) {
-                mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false);
+                if (log) {
+                    mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i),
+                            /* awake */ false);
+                }
                 return false;
             }
         }
 
         if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
-            mLogger.keyguardHideNotification(entry);
+            if (log) mLogger.keyguardHideNotification(entry);
             return false;
         }
 
@@ -388,19 +397,20 @@
      * Common checks for heads up notifications on regular and AOD displays.
      *
      * @param entry the entry to check
+     * @param log whether or not to log the results of these checks
      * @return true if these checks pass, false if the notification should not alert
      */
-    private boolean canAlertHeadsUpCommon(NotificationEntry entry) {
+    private boolean canAlertHeadsUpCommon(NotificationEntry entry, boolean log) {
         StatusBarNotification sbn = entry.getSbn();
 
         // Don't alert notifications that are suppressed due to group alert behavior
         if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            mLogger.logNoAlertingGroupAlertBehavior(entry);
+            if (log) mLogger.logNoAlertingGroupAlertBehavior(entry);
             return false;
         }
 
         if (entry.hasJustLaunchedFullScreenIntent()) {
-            mLogger.logNoAlertingRecentFullscreen(entry);
+            if (log) mLogger.logNoAlertingRecentFullscreen(entry);
             return false;
         }
 
@@ -413,12 +423,14 @@
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    private boolean canAlertAwakeCommon(NotificationEntry entry) {
+    private boolean canAlertAwakeCommon(NotificationEntry entry, boolean log) {
         StatusBarNotification sbn = entry.getSbn();
 
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
-                mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+                if (log) {
+                    mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
+                }
                 return false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ce465bc..2719dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -44,7 +44,8 @@
 import javax.inject.Inject;
 
 /**
- * A global state to track all input states for the algorithm.
+ * Global state to track all input states for
+ * {@link com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm}.
  */
 @SysUISingleton
 public class AmbientState implements Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 4576a64..9900e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -150,7 +150,6 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private PowerManager.WakeLock mWakeLock;
     private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final SessionTracker mSessionTracker;
@@ -262,7 +261,7 @@
             KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             @Main Resources resources,
-            KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
+            KeyguardBypassController keyguardBypassController,
             MetricsLogger metricsLogger, DumpManager dumpManager,
             PowerManager powerManager,
             NotificationMediaManager notificationMediaManager,
@@ -276,7 +275,6 @@
             VibratorHelper vibrator) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
-        mDozeParameters = dozeParameters;
         mUpdateMonitor.registerCallback(this);
         mMediaManager = notificationMediaManager;
         mLatencyTracker = latencyTracker;
@@ -522,7 +520,7 @@
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (!mKeyguardViewController.isShowing()
+            if (!mKeyguardStateController.isShowing()
                     && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
                 if (mKeyguardStateController.isUnlocked()) {
                     return MODE_WAKE_AND_UNLOCK;
@@ -539,7 +537,7 @@
         if (unlockingAllowed && deviceDreaming) {
             return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
         }
-        if (mKeyguardViewController.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed) {
@@ -558,7 +556,7 @@
         boolean bypass = mKeyguardBypassController.getBypassEnabled()
                 || mAuthController.isUdfpsFingerDown();
         if (!mUpdateMonitor.isDeviceInteractive()) {
-            if (!mKeyguardViewController.isShowing()) {
+            if (!mKeyguardStateController.isShowing()) {
                 return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
             } else if (!unlockingAllowed) {
                 return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -582,7 +580,7 @@
         if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
             return MODE_UNLOCK_COLLAPSING;
         }
-        if (mKeyguardViewController.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
                     || mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e30bc68..d6fadca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -363,7 +363,7 @@
             mKeyguardUpdateMonitor.onCameraLaunched();
         }
 
-        if (!mStatusBarKeyguardViewManager.isShowing()) {
+        if (!mKeyguardStateController.isShowing()) {
             final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
             mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
@@ -420,7 +420,7 @@
         // TODO(b/169087248) Possibly add haptics here for emergency action. Currently disabled for
         // app-side haptic experimentation.
 
-        if (!mStatusBarKeyguardViewManager.isShowing()) {
+        if (!mKeyguardStateController.isShowing()) {
             mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
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 cc15c82..b6e658f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -459,7 +459,6 @@
     private final KeyguardStateController mKeyguardStateController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-    private final DynamicPrivacyController mDynamicPrivacyController;
     private final FalsingCollector mFalsingCollector;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -762,7 +761,6 @@
         mHeadsUpManager = headsUpManagerPhone;
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
-        mDynamicPrivacyController = dynamicPrivacyController;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -1553,7 +1551,6 @@
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-        mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
@@ -2062,7 +2059,7 @@
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
-        if (!mStatusBarKeyguardViewManager.isShowing()) {
+        if (!mKeyguardStateController.isShowing()) {
             WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
         }
     }
@@ -2499,8 +2496,8 @@
         };
         // Do not deferKeyguard when occluded because, when keyguard is occluded,
         // we do not launch the activity until keyguard is done.
-        boolean occluded = mStatusBarKeyguardViewManager.isShowing()
-                && mStatusBarKeyguardViewManager.isOccluded();
+        boolean occluded = mKeyguardStateController.isShowing()
+                && mKeyguardStateController.isOccluded();
         boolean deferred = !occluded;
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
                 willLaunchResolverActivity, deferred /* deferred */, animate);
@@ -2570,8 +2567,8 @@
             @Override
             public boolean onDismiss() {
                 if (runnable != null) {
-                    if (mStatusBarKeyguardViewManager.isShowing()
-                            && mStatusBarKeyguardViewManager.isOccluded()) {
+                    if (mKeyguardStateController.isShowing()
+                            && mKeyguardStateController.isOccluded()) {
                         mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
                     } else {
                         mMainExecutor.execute(runnable);
@@ -2665,7 +2662,7 @@
 
     private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
             boolean afterKeyguardGone) {
-        if (mStatusBarKeyguardViewManager.isShowing() && requiresShadeOpen) {
+        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
         dismissKeyguardThenExecute(action, null /* cancelAction */,
@@ -2689,7 +2686,7 @@
             mBiometricUnlockController.startWakeAndUnlock(
                     BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
         }
-        if (mStatusBarKeyguardViewManager.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
                     afterKeyguardGone);
         } else {
@@ -2825,8 +2822,8 @@
     }
 
     private void logStateToEventlog() {
-        boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
-        boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
+        boolean isShowing = mKeyguardStateController.isShowing();
+        boolean isOccluded = mKeyguardStateController.isOccluded();
         boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
         boolean isSecure = mKeyguardStateController.isMethodSecure();
         boolean unlocked = mKeyguardStateController.canDismissLockScreen();
@@ -3222,18 +3219,17 @@
         Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
         Trace.beginSection("CentralSurfaces#updateDozingState");
 
-        boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
-                && !mStatusBarKeyguardViewManager.isOccluded();
+        boolean keyguardVisible = mKeyguardStateController.isVisible();
         // If we're dozing and we'll be animating the screen off, the keyguard isn't currently
         // visible but will be shortly for the animation, so we should proceed as if it's visible.
-        boolean visibleNotOccludedOrWillBe =
-                visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
+        boolean keyguardVisibleOrWillBe =
+                keyguardVisible || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
 
         boolean wakeAndUnlock = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
                 || (mDozing && mDozeParameters.shouldControlScreenOff()
-                && visibleNotOccludedOrWillBe);
+                && keyguardVisibleOrWillBe);
 
         mNotificationPanelViewController.setDozing(mDozing, animate);
         updateQsExpansionEnabled();
@@ -3915,11 +3911,7 @@
 
     @Override
     public boolean isKeyguardShowing() {
-        if (mStatusBarKeyguardViewManager == null) {
-            Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
-            return true;
-        }
-        return mStatusBarKeyguardViewManager.isShowing();
+        return mKeyguardStateController.isShowing();
     }
 
     @Override
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 d4d510f..5f5ec68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -230,8 +230,6 @@
     private View mNotificationContainer;
 
     @Nullable protected KeyguardBouncer mBouncer;
-    protected boolean mShowing;
-    protected boolean mOccluded;
     protected boolean mRemoteInputActive;
     private boolean mGlobalActionsVisible = false;
     private boolean mLastGlobalActionsVisible = false;
@@ -278,10 +276,9 @@
             new KeyguardUpdateMonitorCallback() {
         @Override
         public void onEmergencyCallAction() {
-
             // Since we won't get a setOccluded call we have to reset the view manually such that
             // the bouncer goes away.
-            if (mOccluded) {
+            if (mKeyguardStateController.isOccluded()) {
                 reset(true /* hideBouncerWhenShowing */);
             }
         }
@@ -481,7 +478,7 @@
             } else {
                 mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             }
-        } else if (mShowing && !hideBouncerOverDream) {
+        } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
                     && !mCentralSurfaces.isInLaunchTransition()
@@ -502,7 +499,7 @@
                     mBouncerInteractor.show(/* isScrimmed= */false);
                 }
             }
-        } else if (!mShowing && isBouncerInTransit()) {
+        } else if (!mKeyguardStateController.isShowing()  && isBouncerInTransit()) {
             // Keyguard is not visible anymore, but expansion animation was still running.
             // We need to hide the bouncer, otherwise it will be stuck in transit.
             if (mBouncer != null) {
@@ -535,9 +532,8 @@
     @Override
     public void show(Bundle options) {
         Trace.beginSection("StatusBarKeyguardViewManager#show");
-        mShowing = true;
         mNotificationShadeWindowController.setKeyguardShowing(true);
-        mKeyguardStateController.notifyKeyguardState(mShowing,
+        mKeyguardStateController.notifyKeyguardState(true,
                 mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
@@ -601,7 +597,7 @@
         } else {
             mBouncerInteractor.hide();
         }
-        if (mShowing) {
+        if (mKeyguardStateController.isShowing()) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
             // potential actions unless we actually unlocked.
             cancelPostAuthActions();
@@ -618,7 +614,7 @@
     public void showBouncer(boolean scrimmed) {
         resetAlternateAuth(false);
 
-        if (mShowing && !isBouncerShowing()) {
+        if (mKeyguardStateController.isShowing()  && !isBouncerShowing()) {
             if (mBouncer != null) {
                 mBouncer.show(false /* resetSecuritySelection */, scrimmed);
             } else {
@@ -635,7 +631,7 @@
 
     public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
             boolean afterKeyguardGone, String message) {
-        if (mShowing) {
+        if (mKeyguardStateController.isShowing()) {
             try {
                 Trace.beginSection("StatusBarKeyguardViewManager#dismissWithAction");
                 cancelPendingWakeupAction();
@@ -721,11 +717,12 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
-        if (mShowing) {
+        if (mKeyguardStateController.isShowing()) {
+            final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
-            mNotificationPanelViewController.resetViews(/* animate= */ !mOccluded);
+            mNotificationPanelViewController.resetViews(/* animate= */ !isOccluded);
             // Hide bouncer and quick-quick settings.
-            if (mOccluded && !mDozing) {
+            if (isOccluded && !mDozing) {
                 mCentralSurfaces.hideKeyguard();
                 if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
                     hideBouncer(false /* destroyView */);
@@ -807,7 +804,8 @@
     private void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+            if (dozing || mBouncer.needsFullscreenBouncer()
+                    || mKeyguardStateController.isOccluded()) {
                 reset(dozing /* hideBouncerWhenShowing */);
             }
             updateStates();
@@ -840,18 +838,23 @@
 
     @Override
     public void setOccluded(boolean occluded, boolean animate) {
-        final boolean isOccluding = !mOccluded && occluded;
-        final boolean isUnOccluding = mOccluded && !occluded;
-        setOccludedAndUpdateStates(occluded);
+        final boolean wasOccluded = mKeyguardStateController.isOccluded();
+        final boolean isOccluding = !wasOccluded && occluded;
+        final boolean isUnOccluding = wasOccluded  && !occluded;
+        mKeyguardStateController.notifyKeyguardState(
+                mKeyguardStateController.isShowing(), occluded);
+        updateStates();
+        final boolean isShowing = mKeyguardStateController.isShowing();
+        final boolean isOccluded = mKeyguardStateController.isOccluded();
 
-        if (mShowing && isOccluding) {
+        if (isShowing && isOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
             if (mCentralSurfaces.isInLaunchTransition()) {
                 final Runnable endRunnable = new Runnable() {
                     @Override
                     public void run() {
-                        mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+                        mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
                         reset(true /* hideBouncerWhenShowing */);
                     }
                 };
@@ -866,19 +869,19 @@
                 // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
                 // collapse runnables will be run.
                 mShadeController.get().addPostCollapseAction(() -> {
-                    mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+                    mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
                     reset(true /* hideBouncerWhenShowing */);
                 });
                 return;
             }
-        } else if (mShowing && isUnOccluding) {
+        } else if (isShowing && isUnOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
         }
-        if (mShowing) {
-            mMediaManager.updateMediaMetaData(false, animate && !mOccluded);
+        if (isShowing) {
+            mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
         }
-        mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+        mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
 
         // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
         // no need to reset the keyguard views as we'll be gone shortly. Resetting now could cause
@@ -888,20 +891,11 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
+        if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
             mCentralSurfaces.animateKeyguardUnoccluding();
         }
     }
 
-    private void setOccludedAndUpdateStates(boolean occluded) {
-        mOccluded = occluded;
-        updateStates();
-    }
-
-    public boolean isOccluded() {
-        return mOccluded;
-    }
-
     @Override
     public void startPreHideAnimation(Runnable finishRunnable) {
         if (bouncerIsShowing()) {
@@ -932,8 +926,7 @@
     @Override
     public void hide(long startTime, long fadeoutDuration) {
         Trace.beginSection("StatusBarKeyguardViewManager#hide");
-        mShowing = false;
-        mKeyguardStateController.notifyKeyguardState(mShowing,
+        mKeyguardStateController.notifyKeyguardState(false,
                 mKeyguardStateController.isOccluded());
         launchPendingWakeupAction();
 
@@ -1081,11 +1074,6 @@
                 KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
     }
 
-    @Override
-    public boolean isShowing() {
-        return mShowing;
-    }
-
     /**
      * Returns whether a back invocation can be handled, which depends on whether the keyguard
      * is currently showing (which itself is derived from multiple states).
@@ -1184,8 +1172,8 @@
         if (!mCentralSurfacesRegistered) {
             return;
         }
-        boolean showing = mShowing;
-        boolean occluded = mOccluded;
+        boolean showing = mKeyguardStateController.isShowing();
+        boolean occluded = mKeyguardStateController.isOccluded();
         boolean bouncerShowing = bouncerIsShowing();
         boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
         boolean bouncerDismissible = !isFullscreenBouncer();
@@ -1219,13 +1207,6 @@
             mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
             mCentralSurfaces.setBouncerShowing(bouncerShowing);
         }
-
-        if (occluded != mLastOccluded || mFirstUpdate) {
-            mKeyguardStateController.notifyKeyguardState(showing, occluded);
-        }
-        if (occluded != mLastOccluded || mShowing != showing || mFirstUpdate) {
-            mKeyguardUpdateManager.setKeyguardShowing(showing, occluded);
-        }
         if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
                 || bouncerShowing != mLastBouncerShowing) {
             mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
@@ -1276,12 +1257,12 @@
     public boolean isNavBarVisible() {
         boolean isWakeAndUnlockPulsing = mBiometricUnlockController != null
                 && mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
-        boolean keyguardShowing = mShowing && !mOccluded;
+        boolean keyguardVisible = mKeyguardStateController.isVisible();
         boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing;
-        boolean keyguardWithGestureNav = (keyguardShowing && !mDozing && !mScreenOffAnimationPlaying
+        boolean keyguardWithGestureNav = (keyguardVisible && !mDozing && !mScreenOffAnimationPlaying
                 || mPulsing && !mIsDocked)
                 && mGesturalNav;
-        return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
+        return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
                 || bouncerIsShowing()
                 || mRemoteInputActive
                 || keyguardWithGestureNav
@@ -1413,7 +1394,7 @@
         DismissWithActionRequest request = mPendingWakeupAction;
         mPendingWakeupAction = null;
         if (request != null) {
-            if (mShowing) {
+            if (mKeyguardStateController.isShowing()) {
                 dismissWithAction(request.dismissAction, request.cancelAction,
                         request.afterKeyguardGone, request.message);
             } else if (request.dismissAction != null) {
@@ -1432,10 +1413,10 @@
 
     public boolean bouncerNeedsScrimming() {
         // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
-        return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+        return (mKeyguardStateController.isOccluded()
+                && !mDreamOverlayStateController.isOverlayActive())
                 || bouncerWillDismissWithAction()
-                || (bouncerIsShowing()
-                && bouncerIsScrimmed())
+                || (bouncerIsShowing() && bouncerIsScrimmed())
                 || isFullscreenBouncer();
     }
 
@@ -1454,8 +1435,6 @@
 
     public void dump(PrintWriter pw) {
         pw.println("StatusBarKeyguardViewManager:");
-        pw.println("  mShowing: " + mShowing);
-        pw.println("  mOccluded: " + mOccluded);
         pw.println("  mRemoteInputActive: " + mRemoteInputActive);
         pw.println("  mDozing: " + mDozing);
         pw.println("  mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index c96faab..062c3d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -21,7 +21,9 @@
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel {
     /** A model representing that we have no active wifi network. */
-    object Inactive : WifiNetworkModel()
+    object Inactive : WifiNetworkModel() {
+        override fun toString() = "WifiNetwork.Inactive"
+    }
 
     /**
      * A model representing that our wifi network is actually a carrier merged network, meaning it's
@@ -29,7 +31,9 @@
      *
      * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
      */
-    object CarrierMerged : WifiNetworkModel()
+    object CarrierMerged : WifiNetworkModel() {
+        override fun toString() = "WifiNetwork.CarrierMerged"
+    }
 
     /** Provides information about an active wifi network. */
     data class Active(
@@ -72,6 +76,24 @@
             }
         }
 
+        override fun toString(): String {
+            // Only include the passpoint-related values in the string if we have them. (Most
+            // networks won't have them so they'll be mostly clutter.)
+            val passpointString =
+                if (isPasspointAccessPoint ||
+                    isOnlineSignUpForPasspointAccessPoint ||
+                    passpointProviderFriendlyName != null) {
+                    ", isPasspointAp=$isPasspointAccessPoint, " +
+                        "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " +
+                        "passpointName=$passpointProviderFriendlyName"
+            } else {
+                ""
+            }
+
+            return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " +
+                "level=$level, ssid=$ssid$passpointString)"
+        }
+
         companion object {
             @VisibleForTesting
             internal const val MIN_VALID_LEVEL = 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 250d9d4..1ae1eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -35,9 +35,16 @@
     }
 
     /**
-     * If the lock screen is visible.
-     * The keyguard is also visible when the device is asleep or in always on mode, except when
-     * the screen timed out and the user can unlock by quickly pressing power.
+     * If the keyguard is visible. This is unrelated to being locked or not.
+     */
+    default boolean isVisible() {
+        return isShowing() && !isOccluded();
+    }
+
+    /**
+     * If the keyguard is showing. This includes when it's occluded by an activity, and when
+     * the device is asleep or in always on mode, except when the screen timed out and the user
+     * can unlock by quickly pressing power.
      *
      * This is unrelated to being locked or not.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 437d4d4..cc6fdcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -181,6 +181,7 @@
         if (mShowing == showing && mOccluded == occluded) return;
         mShowing = showing;
         mOccluded = occluded;
+        mKeyguardUpdateMonitor.setKeyguardShowing(showing, occluded);
         Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
                 "Keyguard showing: " + showing + " occluded: " + occluded);
         notifyKeyguardChanged();
@@ -387,6 +388,8 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStateController:");
+        pw.println("  mShowing: " + mShowing);
+        pw.println("  mOccluded: " + mOccluded);
         pw.println("  mSecure: " + mSecure);
         pw.println("  mCanDismissLockScreen: " + mCanDismissLockScreen);
         pw.println("  mTrustManaged: " + mTrustManaged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e0d780a..afcf7a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.window;
 
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -27,6 +30,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -36,6 +40,7 @@
 import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.IWindowManager;
+import android.view.InsetsFrameProvider;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
@@ -221,6 +226,19 @@
         lp.setTitle("StatusBar");
         lp.packageName = mContext.getPackageName();
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        final InsetsFrameProvider gestureInsetsProvider =
+                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES);
+        final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.display_cutout_touchable_region_size);
+        if (safeTouchRegionHeight > 0) {
+            gestureInsetsProvider.minimalInsetsSizeInDisplayCutoutSafe =
+                    Insets.of(0, safeTouchRegionHeight, 0, 0);
+        }
+        lp.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(ITYPE_STATUS_BAR),
+                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+                gestureInsetsProvider
+        };
         return lp;
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 93650b0..4450b76 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -32,6 +32,7 @@
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -61,7 +62,7 @@
     @LayoutRes private val viewLayoutRes: Int,
     private val windowTitle: String,
     private val wakeReason: String,
-) {
+) : CoreStartable(context) {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
      * all subclasses.
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 80385e6..f5cd0ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -41,6 +41,7 @@
 import android.testing.ViewUtils;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceView;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -84,6 +85,7 @@
         MockitoAnnotations.initMocks(this);
 
         mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
+        mKeyguardSecurityContainer.setId(View.generateViewId());
         ViewUtils.attachView(mKeyguardSecurityContainer);
 
         mTestableLooper = TestableLooper.get(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index c1036e3..52f8825 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -21,10 +21,14 @@
 import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
 import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
 import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 
+import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
 
@@ -32,9 +36,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -43,19 +44,17 @@
 import android.graphics.Insets;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
-import android.widget.FrameLayout;
 
+import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.user.data.source.UserRecord;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -64,8 +63,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -78,14 +75,11 @@
 public class KeyguardSecurityContainerTest extends SysuiTestCase {
 
     private static final int VIEW_WIDTH = 1600;
-
-    private int mScreenWidth;
-    private int mFakeMeasureSpec;
+    private static final int VIEW_HEIGHT = 900;
 
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
 
-    @Mock
     private KeyguardSecurityViewFlipper mSecurityViewFlipper;
     @Mock
     private GlobalSettings mGlobalSettings;
@@ -93,59 +87,32 @@
     private FalsingManager mFalsingManager;
     @Mock
     private UserSwitcherController mUserSwitcherController;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Captor
-    private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor;
 
     private KeyguardSecurityContainer mKeyguardSecurityContainer;
-    private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams;
 
     @Before
     public void setup() {
         // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
         // the real references (rather than the TestableResources that this call creates).
         mContext.ensureTestableResources();
-        mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
-                MATCH_PARENT, MATCH_PARENT);
 
-        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+        mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext());
+        mSecurityViewFlipper.setId(View.generateViewId());
         mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+        mKeyguardSecurityContainer.setRight(VIEW_WIDTH);
+        mKeyguardSecurityContainer.setLeft(0);
+        mKeyguardSecurityContainer.setTop(0);
+        mKeyguardSecurityContainer.setBottom(VIEW_HEIGHT);
+        mKeyguardSecurityContainer.setId(View.generateViewId());
         mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
         mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
 
         when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
         when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
-
-        mScreenWidth = getUiDevice().getDisplayWidth();
-        mFakeMeasureSpec = View
-                .MeasureSpec.makeMeasureSpec(mScreenWidth, View.MeasureSpec.EXACTLY);
     }
-
     @Test
-    public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
-        mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
-
-        int halfWidthMeasureSpec =
-                View.MeasureSpec.makeMeasureSpec(mScreenWidth / 2, View.MeasureSpec.EXACTLY);
-        mKeyguardSecurityContainer.onMeasure(mFakeMeasureSpec, mFakeMeasureSpec);
-
-        verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, mFakeMeasureSpec);
-    }
-
-    @Test
-    public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
-
-        mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
-        verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, mFakeMeasureSpec);
-    }
-
-    @Test
-    public void onMeasure_respectsViewInsets() {
+    public void testOnApplyWindowInsets() {
         int paddingBottom = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
         int imeInsetAmount = paddingBottom + 1;
@@ -162,17 +129,12 @@
                 .setInsetsIgnoringVisibility(systemBars(), systemBarInset)
                 .build();
 
-        // It's reduced by the max of the systembar and IME, so just subtract IME inset.
-        int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
-                mScreenWidth - imeInsetAmount, View.MeasureSpec.EXACTLY);
-
         mKeyguardSecurityContainer.onApplyWindowInsets(insets);
-        mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
-        verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+        assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(imeInsetAmount);
     }
 
     @Test
-    public void onMeasure_respectsViewInsets_largerSystembar() {
+    public void testOnApplyWindowInsets_largerSystembar() {
         int imeInsetAmount = 0;
         int paddingBottom = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
@@ -189,25 +151,22 @@
                 .setInsetsIgnoringVisibility(systemBars(), systemBarInset)
                 .build();
 
-        int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
-                mScreenWidth - systemBarInsetAmount, View.MeasureSpec.EXACTLY);
-
         mKeyguardSecurityContainer.onApplyWindowInsets(insets);
-        mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
-        verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
+        assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(systemBarInsetAmount);
     }
 
-    private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
-        int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
-        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+    @Test
+    public void testDefaultViewMode() {
+        mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
                 mUserSwitcherController);
-
-        mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
-        mKeyguardSecurityContainer.layout(0, 0, mScreenWidth, mScreenWidth);
-
-        // Clear any interactions with the mock so we know the interactions definitely come from the
-        // below testing.
-        reset(mSecurityViewFlipper);
+        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
+        ConstraintSet.Constraint viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
     }
 
     @Test
@@ -217,13 +176,22 @@
                 mKeyguardSecurityContainer.getWidth() - 1f);
 
         verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
-        assertSecurityTranslationX(
-                mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+        ConstraintSet.Constraint viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
 
         mKeyguardSecurityContainer.updatePositionByTouchX(1f);
         verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
 
-        verify(mSecurityViewFlipper).setTranslationX(0.0f);
+        viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
+        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
     }
 
     @Test
@@ -232,10 +200,16 @@
 
         mKeyguardSecurityContainer.updatePositionByTouchX(
                 mKeyguardSecurityContainer.getWidth() - 1f);
-        verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+        ConstraintSet.Constraint viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
 
         mKeyguardSecurityContainer.updatePositionByTouchX(1f);
-        verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
+        viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
     }
 
     @Test
@@ -249,17 +223,31 @@
         setupUserSwitcher();
         mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
 
-        // THEN views are oriented side by side
-        assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
-        assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
-        assertSecurityTranslationX(
-                mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
-        assertUserSwitcherTranslationX(0f);
-
+        ConstraintSet.Constraint viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        ConstraintSet.Constraint userSwitcherConstraint =
+                getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
+                R.id.keyguard_bouncer_user_switcher);
+        assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
+                mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
+                getContext().getResources().getDimensionPixelSize(
+                        R.dimen.bouncer_user_switcher_y_trans));
+        assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+        assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
+        assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+        assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
     }
 
     @Test
-    public void testUserSwitcherModeViewGravityPortrait() {
+    public void testUserSwitcherModeViewPositionPortrait() {
         // GIVEN one user has been setup and in landscape
         when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
         Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
@@ -267,15 +255,28 @@
 
         // WHEN UserSwitcherViewMode is initialized and config has changed
         setupUserSwitcher();
-        reset(mSecurityViewFlipper);
-        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
         mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
 
-        // THEN views are both centered horizontally
-        assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
-        assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
-        assertSecurityTranslationX(0);
-        assertUserSwitcherTranslationX(0);
+        ConstraintSet.Constraint viewFlipperConstraint =
+                getViewConstraint(mSecurityViewFlipper.getId());
+        ConstraintSet.Constraint userSwitcherConstraint =
+                getViewConstraint(R.id.keyguard_bouncer_user_switcher);
+
+        assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
+                getContext().getResources().getDimensionPixelSize(
+                        R.dimen.bouncer_user_switcher_y_trans));
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+        assertThat(userSwitcherConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
+        assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
+        assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(WRAP_CONTENT);
+        assertThat(userSwitcherConstraint.layout.mWidth).isEqualTo(WRAP_CONTENT);
     }
 
     @Test
@@ -337,9 +338,9 @@
         when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
         mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
 
-        assertSecurityTranslationX(0);
-        assertUserSwitcherTranslationX(
-                mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+        ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
+                mSecurityViewFlipper.getId());
+        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
     }
 
     private Configuration configuration(@Configuration.Orientation int orientation) {
@@ -348,28 +349,6 @@
         return config;
     }
 
-    private void assertSecurityTranslationX(float translation) {
-        verify(mSecurityViewFlipper).setTranslationX(translation);
-    }
-
-    private void assertUserSwitcherTranslationX(float translation) {
-        ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
-                R.id.keyguard_bouncer_user_switcher);
-        assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
-    }
-
-    private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
-        ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
-                R.id.keyguard_bouncer_user_switcher);
-        assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
-                .isEqualTo(gravity);
-    }
-
-    private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
-        verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
-        assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
-    }
-
     private void setViewWidth(int width) {
         mKeyguardSecurityContainer.setRight(width);
         mKeyguardSecurityContainer.setLeft(0);
@@ -399,9 +378,6 @@
         when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
         mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
                 mGlobalSettings, mFalsingManager, mUserSwitcherController);
-        // reset mSecurityViewFlipper so setup doesn't influence test verifications
-        reset(mSecurityViewFlipper);
-        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
     }
 
     private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -415,4 +391,17 @@
         }
         return users;
     }
+
+    private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
+        int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
+        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
+    }
+
+    /** Get the ConstraintLayout constraint of the view. */
+    private ConstraintSet.Constraint getViewConstraint(int viewId) {
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mKeyguardSecurityContainer);
+        return constraintSet.getConstraint(viewId);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
new file mode 100644
index 0000000..dd9683f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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.assist.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DisplayUtilsTest extends SysuiTestCase {
+
+    @Mock
+    Resources mResources;
+    @Mock
+    Context mMockContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetCornerRadii_noOverlay() {
+        assertEquals(0, DisplayUtils.getCornerRadiusBottom(mContext));
+        assertEquals(0, DisplayUtils.getCornerRadiusTop(mContext));
+    }
+
+    @Test
+    public void testGetCornerRadii_onlyDefaultOverridden() {
+        when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+        when(mMockContext.getResources()).thenReturn(mResources);
+
+        assertEquals(100, DisplayUtils.getCornerRadiusBottom(mMockContext));
+        assertEquals(100, DisplayUtils.getCornerRadiusTop(mMockContext));
+    }
+
+    @Test
+    public void testGetCornerRadii_allOverridden() {
+        when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size)).thenReturn(100);
+        when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_top)).thenReturn(
+                150);
+        when(mResources.getDimensionPixelSize(R.dimen.config_rounded_mask_size_bottom)).thenReturn(
+                200);
+        when(mMockContext.getResources()).thenReturn(mResources);
+
+        assertEquals(200, DisplayUtils.getCornerRadiusBottom(mMockContext));
+        assertEquals(150, DisplayUtils.getCornerRadiusTop(mMockContext));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 11e5880..f210708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.when;
 
 import android.graphics.Rect;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
@@ -688,7 +689,7 @@
     }
 
     @Test
-    public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+    public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
@@ -740,6 +741,56 @@
     }
 
     @Test
+    public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+        // GIVEN AOD interrupt
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mScreenObserver.onScreenTurnedOn();
+        mFgExecutor.runAllReady();
+        mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+        mFgExecutor.runAllReady();
+
+        // Configure UdfpsView to accept the acquired event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+        // WHEN acquired is received
+        mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+                BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricsExecutor.runAllReady();
+        downEvent.recycle();
+
+        // WHEN ACTION_MOVE is received
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricsExecutor.runAllReady();
+        moveEvent.recycle();
+        mFgExecutor.runAllReady();
+
+        // Configure UdfpsView to accept the finger up event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+        // WHEN it times out
+        mFgExecutor.advanceClockToNext();
+        mFgExecutor.runAllReady();
+
+        // THEN the display should be unconfigured once. If the timeout action is not
+        // cancelled, the display would be unconfigured twice which would cause two
+        // FP attempts.
+        verify(mUdfpsView, times(1)).unconfigureDisplay();
+    }
+
+    @Test
     public void aodInterruptScreenOff() throws RemoteException {
         // GIVEN screen off
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index eec33ca..f370be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -27,10 +28,10 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -53,6 +54,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -61,6 +64,7 @@
 public class DreamOverlayServiceTest extends SysuiTestCase {
     private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
             "lowlight");
+    private static final String DREAM_COMPONENT = "package/dream";
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
 
@@ -108,12 +112,14 @@
     @Mock
     UiEventLogger mUiEventLogger;
 
+    @Captor
+    ArgumentCaptor<View> mViewCaptor;
+
     DreamOverlayService mService;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mContext.addMockSystemService(WindowManager.class, mWindowManager);
 
         when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
                 .thenReturn(mDreamOverlayContainerViewController);
@@ -129,7 +135,7 @@
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
-        mService = new DreamOverlayService(mContext, mMainExecutor,
+        mService = new DreamOverlayService(mContext, mMainExecutor, mWindowManager,
                 mDreamOverlayComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
@@ -143,7 +149,8 @@
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
         verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -157,7 +164,8 @@
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
         verify(mWindowManager).addView(any(), any());
@@ -169,7 +177,8 @@
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
         verify(mDreamOverlayContainerViewController).init();
@@ -186,49 +195,76 @@
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
         verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
     }
 
     @Test
-    public void testShouldShowComplicationsFalseByDefault() {
-        mService.onBind(new Intent());
+    public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
-        assertThat(mService.shouldShowComplications()).isFalse();
-    }
-
-    @Test
-    public void testShouldShowComplicationsSetByIntentExtra() {
-        final Intent intent = new Intent();
-        intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true);
-        mService.onBind(intent);
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                true /*shouldShowComplication*/);
 
         assertThat(mService.shouldShowComplications()).isTrue();
     }
 
     @Test
-    public void testLowLightSetByIntentExtra() throws RemoteException {
-        final Intent intent = new Intent();
-        intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT);
-
-        final IBinder proxy = mService.onBind(intent);
+    public void testLowLightSetByStartDream() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
-        assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+                LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
+        assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
         verify(mStateController).setLowLightActive(true);
     }
 
     @Test
-    public void testDestroy() {
+    public void testDestroy() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+                LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        // Verify view added.
+        verify(mWindowManager).addView(mViewCaptor.capture(), any());
+
+        // Service destroyed.
         mService.onDestroy();
         mMainExecutor.runAllReady();
 
+        // Verify view removed.
+        verify(mWindowManager).removeView(mViewCaptor.getValue());
+
+        // Verify state correctly set.
+        verify(mKeyguardUpdateMonitor).removeCallback(any());
+        verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
+        verify(mStateController).setOverlayActive(false);
+        verify(mStateController).setLowLightActive(false);
+    }
+
+    @Test
+    public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
+        // Service destroyed without ever starting dream.
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
+        // Verify no view is removed.
+        verify(mWindowManager, never()).removeView(any());
+
+        // Verify state still correctly set.
         verify(mKeyguardUpdateMonitor).removeCallback(any());
         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
         verify(mStateController).setOverlayActive(false);
@@ -245,7 +281,8 @@
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
 
         // Destroy the service.
         mService.onDestroy();
@@ -255,4 +292,44 @@
 
         verify(mWindowManager, never()).addView(any(), any());
     }
+
+    @Test
+    public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting. Do not show dream complications.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        // Verify that a new window is added.
+        verify(mWindowManager).addView(mViewCaptor.capture(), any());
+        final View windowDecorView = mViewCaptor.getValue();
+
+        // Assert that the overlay is not showing complications.
+        assertThat(mService.shouldShowComplications()).isFalse();
+
+        clearInvocations(mDreamOverlayComponent);
+        clearInvocations(mWindowManager);
+
+        // New dream starting with dream complications showing. Note that when a new dream is
+        // binding to the dream overlay service, it receives the same instance of IBinder as the
+        // first one.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                true /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        // Assert that the overlay is showing complications.
+        assertThat(mService.shouldShowComplications()).isTrue();
+
+        // Verify that the old overlay window has been removed, and a new one created.
+        verify(mWindowManager).removeView(windowDecorView);
+        verify(mWindowManager).addView(any(), any());
+
+        // Verify that new instances of overlay container view controller and overlay touch monitor
+        // are created.
+        verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
+        verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
+    }
 }
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 21c018a..39f3c96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -176,7 +176,7 @@
 
         // and the keyguard goes away
         mViewMediator.setShowingLocked(false);
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
         mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
 
         TestableLooper.get(this).processAllMessages();
@@ -201,7 +201,7 @@
 
         // and the keyguard goes away
         mViewMediator.setShowingLocked(false);
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
         mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
 
         TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 2a13053..d828193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -64,6 +64,7 @@
                 context,
                 FakeExecutor(FakeSystemClock()),
             )
+        mediaTttCommandLineHelper.start()
     }
 
     @Test(expected = IllegalStateException::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index f7b3091..9577274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -110,6 +110,7 @@
             receiverUiEventLogger,
             viewUtil,
         )
+        controllerReceiver.start()
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue).addCallback(callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 213b74a..3a8a51d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -49,7 +49,6 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -84,12 +83,8 @@
     @Mock
     private lateinit var commandQueue: CommandQueue
     @Mock
-    private lateinit var lazyFalsingManager: Lazy<FalsingManager>
-    @Mock
     private lateinit var falsingManager: FalsingManager
     @Mock
-    private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
-    @Mock
     private lateinit var falsingCollector: FalsingCollector
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -119,8 +114,6 @@
         senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
-        whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
-        whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
 
         controllerSender = TestMediaTttChipControllerSender(
             commandQueue,
@@ -132,10 +125,11 @@
             configurationController,
             powerManager,
             senderUiEventLogger,
-            lazyFalsingManager,
-            lazyFalsingCollector,
+            falsingManager,
+            falsingCollector,
             viewUtil,
         )
+        controllerSender.start()
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -441,7 +435,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
-        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
         var undoCallbackCalled = false
         val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {
@@ -457,7 +451,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
-        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
+        whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
         var undoCallbackCalled = false
         val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {
@@ -839,8 +833,8 @@
         configurationController: ConfigurationController,
         powerManager: PowerManager,
         uiEventLogger: MediaTttSenderUiEventLogger,
-        falsingManager: Lazy<FalsingManager>,
-        falsingCollector: Lazy<FalsingCollector>,
+        falsingManager: FalsingManager,
+        falsingCollector: FalsingCollector,
         viewUtil: ViewUtil,
     ) : MediaTttChipControllerSender(
         commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 8073103..6c03730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -39,7 +39,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -49,6 +48,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -113,7 +113,7 @@
         mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
                 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
-                () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardViewController.class),
+                () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mUserTracker, mDumpManager);
 
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index b0cf061..9bf27a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -19,6 +19,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,20 +32,25 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -72,11 +79,14 @@
     private NavigationBarController mNavigationBarController;
     private NavigationBar mDefaultNavBar;
     private NavigationBar mSecondaryNavBar;
+    private StaticMockitoSession mMockitoSession;
 
     @Mock
     private CommandQueue mCommandQueue;
     @Mock
     private NavigationBarComponent.Factory mNavigationBarFactory;
+    @Mock
+    TaskbarDelegate mTaskbarDelegate;
 
     @Before
     public void setUp() {
@@ -90,7 +100,7 @@
                         Dependency.get(Dependency.MAIN_HANDLER),
                         mock(ConfigurationController.class),
                         mock(NavBarHelper.class),
-                        mock(TaskbarDelegate.class),
+                        mTaskbarDelegate,
                         mNavigationBarFactory,
                         mock(StatusBarKeyguardViewManager.class),
                         mock(DumpManager.class),
@@ -100,6 +110,7 @@
                         Optional.of(mock(BackAnimation.class)),
                         mock(FeatureFlags.class)));
         initializeNavigationBars();
+        mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking();
     }
 
     private void initializeNavigationBars() {
@@ -120,6 +131,7 @@
         mNavigationBarController = null;
         mDefaultNavBar = null;
         mSecondaryNavBar = null;
+        mMockitoSession.finishMocking();
     }
 
     @Test
@@ -268,4 +280,22 @@
     public void test3ButtonTaskbarFlagDisabledNoRegister() {
         verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class));
     }
+
+
+    @Test
+    public void testConfigurationChange_taskbarNotInitialized() {
+        Configuration configuration = mContext.getResources().getConfiguration();
+        when(Utilities.isTablet(any())).thenReturn(true);
+        mNavigationBarController.onConfigChanged(configuration);
+        verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
+    }
+
+    @Test
+    public void testConfigurationChange_taskbarInitialized() {
+        Configuration configuration = mContext.getResources().getConfiguration();
+        when(Utilities.isTablet(any())).thenReturn(true);
+        when(mTaskbarDelegate.isInitialized()).thenReturn(true);
+        mNavigationBarController.onConfigChanged(configuration);
+        verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 51f0953..0e9d279 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -72,7 +72,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -194,7 +193,7 @@
     @Mock
     private CentralSurfaces mCentralSurfaces;
     @Mock
-    private KeyguardViewController mKeyguardViewController;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private UserContextProvider mUserContextProvider;
     @Mock
@@ -240,7 +239,7 @@
                     mock(AccessibilityButtonTargetsObserver.class),
                     mSystemActions, mOverviewProxyService,
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
-                    mKeyguardViewController, mock(NavigationModeController.class),
+                    mKeyguardStateController, mock(NavigationModeController.class),
                     mock(UserTracker.class), mock(DumpManager.class)));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -380,7 +379,7 @@
 
         // Verify navbar didn't alter and showing back icon when the keyguard is showing without
         // requesting IME insets visible.
-        doReturn(true).when(mKeyguardViewController).isShowing();
+        doReturn(true).when(mKeyguardStateController).isShowing();
         mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
                 BACK_DISPOSITION_DEFAULT, true);
         assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index b719c7f..a6381d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -32,7 +32,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
@@ -58,8 +57,6 @@
         mDynamicPrivacyController = new DynamicPrivacyController(
                 mLockScreenUserManager, mKeyguardStateController,
                 mock(StatusBarStateController.class));
-        mDynamicPrivacyController.setStatusBarKeyguardViewManager(
-                mock(StatusBarKeyguardViewManager.class));
         mDynamicPrivacyController.addListener(mListener);
         // Disable dynamic privacy by default
         allowNotificationsInPublic(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 365e608..4dea6be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -95,8 +95,6 @@
     @Mock
     private AuthController mAuthController;
     @Mock
-    private DozeParameters mDozeParameters;
-    @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
     private NotificationMediaManager mNotificationMediaManager;
@@ -120,7 +118,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         TestableResources res = getContext().getOrCreateTestableResources();
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
@@ -132,7 +130,7 @@
         mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
                 mKeyguardViewMediator, mScrimController,
                 mNotificationShadeWindowController, mKeyguardStateController, mHandler,
-                mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
+                mUpdateMonitor, res.getResources(), mKeyguardBypassController,
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController,
@@ -170,7 +168,7 @@
     public void onBiometricAuthenticated_whenFingerprintAndNotInteractive_wakeAndUnlock() {
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
@@ -187,7 +185,7 @@
     public void onBiometricAuthenticated_whenDeviceIsAlreadyUnlocked_wakeAndUnlock() {
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
         when(mKeyguardStateController.isUnlocked()).thenReturn(true);
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 05f8760..ad497a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -516,32 +516,32 @@
 
     @Test
     public void executeRunnableDismissingKeyguard_nullRunnable_showingAndOccluded() {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
     @Test
     public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
         mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
     @Test
     public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
 
         mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
     @Test
     public void executeRunnableDismissingKeyguard_dreaming_notShowing() throws RemoteException {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
 
         mCentralSurfaces.executeRunnableDismissingKeyguard(() ->  {},
@@ -555,8 +555,8 @@
 
     @Test
     public void executeRunnableDismissingKeyguard_notDreaming_notShowing() throws RemoteException {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(false);
 
         mCentralSurfaces.executeRunnableDismissingKeyguard(() ->  {},
@@ -571,10 +571,10 @@
     @Test
     public void lockscreenStateMetrics_notShowing() {
         // uninteresting state, except that fingerprint must be non-zero
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
         when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
         mCentralSurfaces.onKeyguardViewManagerStatesUpdated();
@@ -589,10 +589,10 @@
     @Test
     public void lockscreenStateMetrics_notShowing_secure() {
         // uninteresting state, except that fingerprint must be non-zero
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
@@ -608,10 +608,10 @@
     @Test
     public void lockscreenStateMetrics_isShowing() {
         // uninteresting state, except that fingerprint must be non-zero
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
         when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
 
@@ -627,10 +627,10 @@
     @Test
     public void lockscreenStateMetrics_isShowing_secure() {
         // uninteresting state, except that fingerprint must be non-zero
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
@@ -646,10 +646,10 @@
     @Test
     public void lockscreenStateMetrics_isShowingBouncer() {
         // uninteresting state, except that fingerprint must be non-zero
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
         when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
@@ -1053,9 +1053,9 @@
     }
 
     @Test
-    public void startActivityDismissingKeyguard_isShowingandIsOccluded() {
-        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+    public void startActivityDismissingKeyguard_isShowingAndIsOccluded() {
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
         mCentralSurfaces.startActivityDismissingKeyguard(
                 new Intent(),
                 /* onlyProvisioned = */false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
new file mode 100644
index 0000000..a986777
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 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.statusbar.phone;
+
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Mock implementation of KeyguardStateController which tracks showing and occluded states
+ * based on {@link #notifyKeyguardState(boolean showing, boolean occluded)}}.
+ */
+public class FakeKeyguardStateController implements KeyguardStateController {
+    private boolean mShowing;
+    private boolean mOccluded;
+    private boolean mCanDismissLockScreen;
+
+    @Override
+    public void notifyKeyguardState(boolean showing, boolean occluded) {
+        mShowing = showing;
+        mOccluded = occluded;
+    }
+
+    @Override
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    @Override
+    public boolean isOccluded() {
+        return mOccluded;
+    }
+
+    public void setCanDismissLockScreen(boolean canDismissLockScreen) {
+        mCanDismissLockScreen = canDismissLockScreen;
+    }
+
+    @Override
+    public boolean canDismissLockScreen() {
+        return mCanDismissLockScreen;
+    }
+
+    @Override
+    public boolean isBouncerShowing() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardScreenRotationAllowed() {
+        return false;
+    }
+
+    @Override
+    public boolean isMethodSecure() {
+        return true;
+    }
+
+    @Override
+    public boolean isTrusted() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardGoingAway() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardFadingAway() {
+        return false;
+    }
+
+    @Override
+    public boolean isLaunchTransitionFadingAway() {
+        return false;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDuration() {
+        return 0;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDelay() {
+        return 0;
+    }
+
+    @Override
+    public long calculateGoingToFullShadeDelay() {
+        return 0;
+    }
+
+    @Override
+    public float getDismissAmount() {
+        return 0f;
+    }
+
+    @Override
+    public boolean isDismissingFromSwipe() {
+        return false;
+    }
+
+    @Override
+    public boolean isFlingingToDismissKeyguard() {
+        return false;
+    }
+
+    @Override
+    public boolean isFlingingToDismissKeyguardDuringSwipeGesture() {
+        return false;
+    }
+
+    @Override
+    public boolean isSnappingKeyguardBackAfterSwipe() {
+        return false;
+    }
+
+    @Override
+    public void notifyPanelFlingStart(boolean dismiss) {
+    }
+
+    @Override
+    public void notifyPanelFlingEnd() {
+    }
+
+    @Override
+    public void addCallback(Callback listener) {
+    }
+
+    @Override
+    public void removeCallback(Callback listener) {
+    }
+}
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 ecbb7e5b..8da8d04 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
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -68,7 +69,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
 import com.google.common.truth.Truth;
@@ -94,7 +94,6 @@
 
     @Mock private ViewMediatorCallback mViewMediatorCallback;
     @Mock private LockPatternUtils mLockPatternUtils;
-    @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private ViewGroup mContainer;
     @Mock private NotificationPanelViewController mNotificationPanelView;
@@ -123,6 +122,8 @@
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+    private FakeKeyguardStateController mKeyguardStateController =
+            spy(new FakeKeyguardStateController());
 
     @Mock private ViewRootImpl mViewRootImpl;
     @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@@ -180,7 +181,6 @@
                 mBiometricUnlockController,
                 mNotificationContainer,
                 mBypassController);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         mStatusBarKeyguardViewManager.show(null);
         ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
                 ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
@@ -253,7 +253,7 @@
 
     @Test
     public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+        mKeyguardStateController.setCanDismissLockScreen(false);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
         verify(mBouncer).show(eq(false), eq(false));
 
@@ -340,13 +340,12 @@
     }
 
     @Test
-    public void setOccluded_onKeyguardOccludedChangedCalledCorrectly() {
+    public void setOccluded_onKeyguardOccludedChangedCalled() {
         clearInvocations(mKeyguardStateController);
         clearInvocations(mKeyguardUpdateMonitor);
 
-        // Should be false to start, so no invocations
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
-        verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
 
         clearInvocations(mKeyguardUpdateMonitor);
         clearInvocations(mKeyguardStateController);
@@ -357,8 +356,8 @@
         clearInvocations(mKeyguardUpdateMonitor);
         clearInvocations(mKeyguardStateController);
 
-        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
-        verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
     }
 
     @Test
@@ -426,7 +425,7 @@
         when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
         assertTrue(
                 "Is showing not accurate when alternative auth showing",
-                mStatusBarKeyguardViewManager.isShowing());
+                mStatusBarKeyguardViewManager.isBouncerShowing());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 929e529..a3ad028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,9 +23,9 @@
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -144,7 +144,12 @@
 
         /** A function that, given a context, calculates the correct content description string. */
         val contentDescription: (Context) -> String,
-    )
+
+        /** A human-readable description used for the test names. */
+        val description: String,
+    ) {
+        override fun toString() = description
+    }
 
     // Note: We use default values for the boolean parameters to reflect a "typical configuration"
     //   for wifi. This allows each TestCase to only define the parameter values that are critical
@@ -158,12 +163,21 @@
 
         /** The expected output. Null if we expect the output to be null. */
         val expected: Expected?
-    )
+    ) {
+        override fun toString(): String {
+            return "when INPUT(enabled=$enabled, " +
+                "forceHidden=$forceHidden, " +
+                "showWhenEnabled=$alwaysShowIconWhenEnabled, " +
+                "hasDataCaps=$hasDataCapabilities, " +
+                "network=$network) then " +
+                "EXPECTED($expected)"
+        }
+    }
 
     companion object {
-        @Parameters(name = "{0}")
-        @JvmStatic
-        fun data(): Collection<TestCase> =
+        @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+        private val testData: List<TestCase> =
             listOf(
                 // Enabled = false => no networks shown
                 TestCase(
@@ -215,11 +229,12 @@
                     network = WifiNetworkModel.Inactive,
                     expected =
                         Expected(
-                            iconResource = WifiIcons.WIFI_NO_NETWORK,
+                            iconResource = WIFI_NO_NETWORK,
                             contentDescription = { context ->
                                 "${context.getString(WIFI_NO_CONNECTION)}," +
                                     context.getString(NO_INTERNET)
-                            }
+                            },
+                            description = "No network icon",
                         ),
                 ),
                 TestCase(
@@ -231,7 +246,8 @@
                             contentDescription = { context ->
                                 "${context.getString(WIFI_CONNECTION_STRENGTH[4])}," +
                                     context.getString(NO_INTERNET)
-                            }
+                            },
+                            description = "No internet level 4 icon",
                         ),
                 ),
                 TestCase(
@@ -242,7 +258,8 @@
                             iconResource = WIFI_FULL_ICONS[2],
                             contentDescription = { context ->
                                 context.getString(WIFI_CONNECTION_STRENGTH[2])
-                            }
+                            },
+                            description = "Full internet level 2 icon",
                         ),
                 ),
 
@@ -252,11 +269,12 @@
                     network = WifiNetworkModel.Inactive,
                     expected =
                         Expected(
-                            iconResource = WifiIcons.WIFI_NO_NETWORK,
+                            iconResource = WIFI_NO_NETWORK,
                             contentDescription = { context ->
                                 "${context.getString(WIFI_NO_CONNECTION)}," +
                                     context.getString(NO_INTERNET)
-                            }
+                            },
+                            description = "No network icon",
                         ),
                 ),
                 TestCase(
@@ -268,7 +286,8 @@
                             contentDescription = { context ->
                                 "${context.getString(WIFI_CONNECTION_STRENGTH[2])}," +
                                     context.getString(NO_INTERNET)
-                            }
+                            },
+                            description = "No internet level 2 icon",
                         ),
                 ),
                 TestCase(
@@ -279,7 +298,8 @@
                             iconResource = WIFI_FULL_ICONS[0],
                             contentDescription = { context ->
                                 context.getString(WIFI_CONNECTION_STRENGTH[0])
-                            }
+                            },
+                            description = "Full internet level 0 icon",
                         ),
                 ),
 
@@ -309,7 +329,8 @@
                             iconResource = WIFI_FULL_ICONS[4],
                             contentDescription = { context ->
                                 context.getString(WIFI_CONNECTION_STRENGTH[4])
-                            }
+                            },
+                            description = "Full internet level 4 icon",
                         ),
                 ),
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index e1c1b46..c4abedd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -260,6 +260,9 @@
         var mostRecentViewInfo: ViewInfo? = null
 
         override val windowLayoutParams = commonWindowLayoutParams
+
+        override fun start() {}
+
         override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
             mostRecentViewInfo = newInfo
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
index 15ba672..4ca1fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.junit.Assert.assertTrue
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -35,6 +36,7 @@
 
     private val serializer = IpcSerializer()
 
+    @Ignore("b/253046405")
     @Test
     fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
         val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 0ab5a8a..80d9d97 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -270,7 +270,8 @@
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
             association = mService.createAssociation(userId, packageName, macAddress,
-                    request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged());
+                    request.getDisplayName(), request.getDeviceProfile(),
+                    request.getAssociatedDevice(), request.isSelfManaged());
         } finally {
             Binder.restoreCallingIdentity(callingIdentity);
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0426c79..f2cb602 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -55,6 +55,7 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.DeviceNotAssociatedException;
@@ -872,23 +873,25 @@
 
     /**
      * @deprecated use
-     * {@link #createAssociation(int, String, MacAddress, CharSequence, String, boolean)}
+     * {@link #createAssociation(int, String, MacAddress, CharSequence, String, AssociatedDevice,
+     * boolean)}
      */
     @Deprecated
     void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress,
             @NonNull String packageName, @Nullable String deviceProfile) {
         final MacAddress macAddress = MacAddress.fromString(deviceMacAddress);
-        createAssociation(userId, packageName, macAddress, null, deviceProfile, false);
+        createAssociation(userId, packageName, macAddress, null, deviceProfile, null, false);
     }
 
     AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName,
             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
-            @Nullable String deviceProfile, boolean selfManaged) {
+            @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+            boolean selfManaged) {
         final int id = getNewAssociationIdForPackage(userId, packageName);
         final long timestamp = System.currentTimeMillis();
 
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
-                macAddress, displayName, deviceProfile, selfManaged,
+                macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
                 /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
         Slog.i(TAG, "New CDM association created=" + association);
         mAssociationStore.addAssociation(association);
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 4b56c1b..c4f5766 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -229,6 +229,11 @@
     /**
      * Reads previously persisted data for the given user "into" the provided containers.
      *
+     * Note that {@link AssociationInfo#getAssociatedDevice()} will always be {@code null} after
+     * retrieval from this datastore because it is not persisted (by design). This means that
+     * persisted data is not guaranteed to be identical to the initial data that was stored at the
+     * time of association.
+     *
      * @param userId Android UserID
      * @param associationsOut a container to read the {@link AssociationInfo}s "into".
      * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
@@ -293,6 +298,9 @@
     /**
      * Persisted data to the disk.
      *
+     * Note that associatedDevice field in {@link AssociationInfo} is not persisted by this
+     * datastore implementation.
+     *
      * @param userId Android UserID
      * @param associations a set of user's associations.
      * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
@@ -419,7 +427,7 @@
         final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
 
         out.add(new AssociationInfo(associationId, userId, appPackage,
-                MacAddress.fromString(deviceAddress), null, profile,
+                MacAddress.fromString(deviceAddress), null, profile, null,
                 /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
                 Long.MAX_VALUE));
     }
@@ -556,9 +564,11 @@
             boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
         AssociationInfo associationInfo = null;
         try {
+            // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
+            // datastore is not guaranteed to be identical to the one from initial association.
             associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
-                    displayName, profile, selfManaged, notify, revoked, timeApproved,
-                    lastTimeConnected);
+                    displayName, profile, null, selfManaged, notify, revoked,
+                    timeApproved, lastTimeConnected);
         } catch (Exception e) {
             if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
         }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4018be1..0f101b0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -42,6 +42,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
+import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.ContentResolver;
@@ -916,13 +917,16 @@
             return mGlobalContentCaptureOptions.getOptions(userId, packageName);
         }
 
+        // ErrorProne says ContentCaptureManagerService.this.mLock needs to be guarded by
+        // 'service.mLock', which is the same as mLock.
+        @SuppressWarnings("GuardedBy")
         @Override
         public void notifyActivityEvent(int userId, @NonNull ComponentName activityComponent,
-                @ActivityEventType int eventType) {
+                @ActivityEventType int eventType, @NonNull ActivityId activityId) {
             synchronized (mLock) {
                 final ContentCapturePerUserService service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.onActivityEventLocked(activityComponent, eventType);
+                    service.onActivityEventLocked(activityId, activityComponent, eventType);
                 }
             }
         }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index a08687f..cee78e0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -548,12 +548,13 @@
     }
 
     @GuardedBy("mLock")
-    void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
+    void onActivityEventLocked(@NonNull ActivityId activityId,
+            @NonNull ComponentName componentName, @ActivityEventType int type) {
         if (mRemoteService == null) {
             if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
             return;
         }
-        final ActivityEvent event = new ActivityEvent(componentName, type);
+        final ActivityEvent event = new ActivityEvent(activityId, componentName, type);
 
         if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event);
 
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 285c406..41044bf 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -16,12 +16,18 @@
 
 package android.os;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
 import com.android.internal.os.BinderCallsStats;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
 import java.util.List;
 
+
 /**
  * Battery stats local system service interface. This is used to pass internal data out of
  * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
@@ -29,6 +35,19 @@
  * @hide Only for use within Android OS.
  */
 public abstract class BatteryStatsInternal {
+
+    public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1;
+    public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
+            CPU_WAKEUP_SUBSYSTEM_UNKNOWN,
+            CPU_WAKEUP_SUBSYSTEM_ALARM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface CpuWakeupSubsystem {
+    }
+
     /**
      * Returns the wifi interfaces.
      */
@@ -56,9 +75,10 @@
     /**
      * Inform battery stats how many deferred jobs existed when the app got launched and how
      * long ago was the last job execution for the app.
-     * @param uid the uid of the app.
+     *
+     * @param uid         the uid of the app.
      * @param numDeferred number of deferred jobs.
-     * @param sinceLast how long in millis has it been since a job was run
+     * @param sinceLast   how long in millis has it been since a job was run
      */
     public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast);
 
@@ -72,4 +92,11 @@
      * Informs battery stats of native thread IDs of threads taking incoming binder calls.
      */
     public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
+
+    /**
+     * Reports any activity that could potentially have caused the CPU to wake up.
+     * Accepts a timestamp to allow the reporter to report it before or after the event.
+     */
+    public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem,
+            long elapsedMillis, @NonNull int... uids);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b34fe69..c837051 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -204,6 +204,7 @@
 import android.app.ProfilerInfo;
 import android.app.SyncNotedAppOp;
 import android.app.WaitResult;
+import android.app.assist.ActivityId;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManager;
 import android.app.compat.CompatChanges;
@@ -2915,7 +2916,7 @@
      * @param taskRoot Task's root
      */
     public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-            IBinder appToken, ComponentName taskRoot) {
+            IBinder appToken, ComponentName taskRoot, ActivityId activityId) {
         if (DEBUG_SWITCH) {
             Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
                     + activity + " hash=" + appToken.hashCode() + " event=" + event);
@@ -2932,7 +2933,7 @@
         if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED
                 || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED
                 || event == Event.ACTIVITY_DESTROYED)) {
-            contentCaptureService.notifyActivityEvent(userId, activity, event);
+            contentCaptureService.notifyActivityEvent(userId, activity, event, activityId);
         }
         // Currently we have move most of logic to the client side. When the activity lifecycle
         // event changed, the client side will notify the VoiceInteractionManagerService. But
@@ -5285,7 +5286,7 @@
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
             SystemProperties.set("dev.bootcomplete", "1");
-            mUserController.sendBootCompleted(
+            mUserController.onBootComplete(
                     new IIntentReceiver.Stub() {
                         @Override
                         public void performReceive(Intent intent, int resultCode,
@@ -17106,9 +17107,9 @@
 
         @Override
         public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-                IBinder appToken, ComponentName taskRoot) {
+                IBinder appToken, ComponentName taskRoot, ActivityId activityId) {
             ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
-                    appToken, taskRoot);
+                    appToken, taskRoot, activityId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d9d29d65..75d1f68 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -81,9 +81,11 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.StatsEvent;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
@@ -104,6 +106,7 @@
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
 import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.CpuWakeupStats;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
 import java.io.File;
@@ -120,6 +123,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -142,6 +146,8 @@
 
     private final PowerProfile mPowerProfile;
     final BatteryStatsImpl mStats;
+    @GuardedBy("mWakeupStats")
+    final CpuWakeupStats mCpuWakeupStats;
     private final BatteryUsageStatsStore mBatteryUsageStatsStore;
     private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
     private final Context mContext;
@@ -373,6 +379,7 @@
         }
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
                 mBatteryUsageStatsStore);
+        mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map);
     }
 
     public void publish() {
@@ -464,6 +471,12 @@
                 mStats.noteBinderThreadNativeIds(binderThreadNativeTids);
             }
         }
+
+        @Override
+        public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) {
+            Objects.requireNonNull(uids);
+            mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids);
+        }
     }
 
     @Override
@@ -2251,12 +2264,13 @@
             try {
                 String reason;
                 while ((reason = waitWakeup()) != null) {
+                    final long nowElapsed = SystemClock.elapsedRealtime();
+                    final long nowUptime = SystemClock.uptimeMillis();
                     // Wait for the completion of pending works if there is any
                     awaitCompletion();
-
+                    mCpuWakeupStats.noteWakeupTimeAndReason(nowElapsed, nowUptime, reason);
                     synchronized (mStats) {
-                        mStats.noteWakeupReasonLocked(reason,
-                                SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+                        mStats.noteWakeupReasonLocked(reason, nowElapsed, nowUptime);
                     }
                 }
             } catch (RuntimeException e) {
@@ -2312,6 +2326,7 @@
         pw.println("  --read-daily: read-load last written daily stats.");
         pw.println("  --settings: dump the settings key/values related to batterystats");
         pw.println("  --cpu: dump cpu stats for debugging purpose");
+        pw.println("  --wakeups: dump CPU wakeup history and attribution.");
         pw.println("  --power-profile: dump the power profile constants");
         pw.println("  --usage: write battery usage stats. Optional arguments:");
         pw.println("     --proto: output as a binary protobuffer");
@@ -2567,6 +2582,10 @@
                     }
                     dumpUsageStatsToProto(fd, pw, model, proto);
                     return;
+                } else if ("--wakeups".equals(arg)) {
+                    mCpuWakeupStats.dump(new IndentingPrintWriter(pw, "  "),
+                            SystemClock.elapsedRealtime());
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2697,12 +2716,16 @@
         } else {
             if (DBG) Slog.d(TAG, "begin dumpLocked from UID " + Binder.getCallingUid());
             awaitCompletion();
+
             synchronized (mStats) {
                 mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart);
                 if (writeData) {
                     mStats.writeAsyncLocked();
                 }
             }
+            pw.println();
+            mCpuWakeupStats.dump(new IndentingPrintWriter(pw, "  "), SystemClock.elapsedRealtime());
+
             if (DBG) Slog.d(TAG, "end dumpLocked");
         }
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 226c638..216a48e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -439,11 +439,6 @@
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = mInjector.getLockPatternUtils();
         updateStartedUserArrayLU();
-
-        // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM
-        // (Headless System User Mode), but on master it will be used by all devices (and hence this
-        // initial assignment should be removed).
-        mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode();
     }
 
     void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
@@ -602,8 +597,11 @@
             if (!mInjector.getUserManager().isPreCreated(userId)) {
                 mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
                         userId, 0));
-                // In case of headless system user mode, do not send boot complete broadcast for
-                // system user as it is sent by sendBootCompleted call.
+                // The "locked boot complete" broadcast for the system user is supposed be sent when
+                // the device has finished booting.  Normally, that is the same time that the system
+                // user transitions to RUNNING_LOCKED.  However, in "headless system user mode", the
+                // system user is explicitly started before the device has finished booting.  In
+                // that case, we need to wait until onBootComplete() to send the broadcast.
                 if (!(UserManager.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
                     // ACTION_LOCKED_BOOT_COMPLETED
                     sendLockedBootCompletedBroadcast(resultTo, userId);
@@ -1808,15 +1806,13 @@
      */
     private boolean maybeUnlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {
 
-        // Delay user unlocking for headless system user mode until the system boot
-        // completes. When the system boot completes, the {@link #onBootCompleted()}
-        // method unlocks all started users for headless system user mode. This is done
-        // to prevent unlocking the users too early during the system boot up.
-        // Otherwise, emulated volumes are mounted too early during the system
-        // boot up. When vold is reset on boot complete, vold kills all apps/services
-        // (that use these emulated volumes) before unmounting the volumes(b/241929666).
-        // In the past, these killings have caused the system to become too unstable on
-        // some occasions.
+        // We cannot allow users to be unlocked before PHASE_BOOT_COMPLETED, for two reasons.
+        // First, emulated volumes aren't supposed to be used until then; StorageManagerService
+        // assumes it can reset everything upon reaching PHASE_BOOT_COMPLETED.  Second, on some
+        // devices the Weaver HAL needed to unlock the user's storage isn't available until sometime
+        // shortly before PHASE_BOOT_COMPLETED.  The below logic enforces a consistent flow across
+        // all devices, regardless of their Weaver implementation.
+        //
         // Any unlocks that get delayed by this will be done by onBootComplete() instead.
         if (!mAllowUserUnlocking) {
             Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId);
@@ -2424,11 +2420,8 @@
         }
     }
 
-    /**
-     * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted
-     */
-    @Deprecated
-    private void onBootCompletedOnHeadlessSystemUserModeDevices() {
+    void onBootComplete(IIntentReceiver resultTo) {
+        // Now that PHASE_BOOT_COMPLETED has been reached, user unlocking is allowed.
         setAllowUserUnlocking(true);
 
         // Get a copy of mStartedUsers to use outside of lock.
@@ -2436,37 +2429,30 @@
         synchronized (mLock) {
             startedUsers = mStartedUsers.clone();
         }
+        // In non-headless system user mode, call finishUserBoot() to transition the system user
+        // from the BOOTING state to RUNNING_LOCKED, then to RUNNING_UNLOCKED if possible.
+        //
+        // In headless system user mode, additional users may have been started, and all users
+        // (including the system user) that ever get started are started explicitly.  In this case,
+        // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
+        // doesn't handle issuing the needed onUserStarting() call, and it would just race with an
+        // explicit start anyway.  We do, however, need to send the "locked boot complete" broadcast
+        // for the system user, as that got skipped earlier due to the *device* boot not being
+        // complete yet.  We also need to try to unlock all started users, since until now explicit
+        // user starts didn't proceed to unlocking, due to it being too early in the device boot.
+        //
         // USER_SYSTEM must be processed first.  It will be first in the array, as its ID is lowest.
         Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
         for (int i = 0; i < startedUsers.size(); i++) {
-            UserState uss = startedUsers.valueAt(i);
-            int userId = uss.mHandle.getIdentifier();
-            Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId);
-            maybeUnlockUser(userId);
-        }
-    }
-
-    void sendBootCompleted(IIntentReceiver resultTo) {
-        if (UserManager.isHeadlessSystemUserMode()) {
-            // Unlocking users is delayed until boot complete for headless system user mode.
-            onBootCompletedOnHeadlessSystemUserModeDevices();
-        }
-
-        // Get a copy of mStartedUsers to use outside of lock
-        SparseArray<UserState> startedUsers;
-        synchronized (mLock) {
-            startedUsers = mStartedUsers.clone();
-        }
-        for (int i = 0; i < startedUsers.size(); i++) {
+            int userId = startedUsers.keyAt(i);
             UserState uss = startedUsers.valueAt(i);
             if (!UserManager.isHeadlessSystemUserMode()) {
                 finishUserBoot(uss, resultTo);
-            } else if (uss.mHandle.isSystem()) {
-                // In case of headless system user mode, send only locked boot complete broadcast
-                // for system user since finishUserBoot call will be made using other code path;
-                // for non-system user, do nothing since finishUserBoot will be called elsewhere.
-                sendLockedBootCompletedBroadcast(resultTo, uss.mHandle.getIdentifier());
-                return;
+            } else {
+                if (userId == UserHandle.USER_SYSTEM) {
+                    sendLockedBootCompletedBroadcast(resultTo, userId);
+                }
+                maybeUnlockUser(userId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6a53978..6795b6b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,6 +21,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -51,6 +52,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.DnsResolver;
 import android.net.INetd;
@@ -234,6 +236,7 @@
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
     private final AppOpsManager mAppOpsManager;
+    private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
     // The context is for specific user which is created from mUserId
     private final Context mUserIdContext;
     @VisibleForTesting final Dependencies mDeps;
@@ -549,6 +552,8 @@
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+        mConnectivityDiagnosticsManager =
+                mContext.getSystemService(ConnectivityDiagnosticsManager.class);
         mDeps = deps;
         mNms = netService;
         mNetd = netd;
@@ -2739,6 +2744,7 @@
 
         @Nullable private IkeSessionWrapper mSession;
         @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
+        @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
 
         // mMobikeEnabled can only be updated after IKE AUTH is finished.
         private boolean mMobikeEnabled = false;
@@ -2797,6 +2803,15 @@
                 mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
                         new Handler(mLooper));
             }
+
+            // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
+            // Network object.
+            final NetworkRequest diagRequest = new NetworkRequest.Builder()
+                    .addTransportType(TRANSPORT_VPN)
+                    .removeCapability(NET_CAPABILITY_NOT_VPN).build();
+            mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
+            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    diagRequest, mExecutor, mDiagnosticsCallback);
         }
 
         private boolean isActiveNetwork(@Nullable Network network) {
@@ -3066,22 +3081,28 @@
                 return;
             }
 
+            if (maybeMigrateIkeSession(underlyingNetwork)) return;
+
+            startIkeSession(underlyingNetwork);
+        }
+
+        boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) {
+            if (mSession == null || !mMobikeEnabled) return false;
+
+            // IKE session can schedule a migration event only when IKE AUTH is finished
+            // and mMobikeEnabled is true.
+            Log.d(TAG, "Migrate IKE Session with token "
+                    + mCurrentToken
+                    + " to network "
+                    + underlyingNetwork);
+            mSession.setNetwork(underlyingNetwork);
+            return true;
+        }
+
+        private void startIkeSession(@NonNull Network underlyingNetwork) {
+            Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+
             try {
-                if (mSession != null && mMobikeEnabled) {
-                    // IKE session can schedule a migration event only when IKE AUTH is finished
-                    // and mMobikeEnabled is true.
-                    Log.d(
-                            TAG,
-                            "Migrate IKE Session with token "
-                                    + mCurrentToken
-                                    + " to network "
-                                    + underlyingNetwork);
-                    mSession.setNetwork(underlyingNetwork);
-                    return;
-                }
-
-                Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
-
                 // Clear mInterface to prevent Ikev2VpnRunner being cleared when
                 // interfaceRemoved() is called.
                 synchronized (Vpn.this) {
@@ -3169,6 +3190,28 @@
             mUnderlyingLinkProperties = lp;
         }
 
+        class VpnConnectivityDiagnosticsCallback
+                extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+            // The callback runs in the executor thread.
+            @Override
+            public void onDataStallSuspected(
+                    ConnectivityDiagnosticsManager.DataStallReport report) {
+                synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
+
+                    // Handle the report only for current VPN network.
+                    if (mNetworkAgent != null
+                            && mNetworkAgent.getNetwork().equals(report.getNetwork())) {
+                        Log.d(TAG, "Data stall suspected");
+
+                        // Trigger MOBIKE.
+                        maybeMigrateIkeSession(mActiveNetwork);
+                    }
+                }
+            }
+        }
+
         /**
          * Handles loss of the default underlying network
          *
@@ -3463,6 +3506,8 @@
             resetIkeState();
 
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+            mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+                    mDiagnosticsCallback);
 
             mExecutor.shutdown();
         }
diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
index c33b5f1..0812fd9 100644
--- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
+++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.os.Bundle;
@@ -61,5 +62,6 @@
      * Notifies the intelligence service of a high-level activity event for the given user.
      */
     public abstract void notifyActivityEvent(@UserIdInt int userId,
-            @NonNull ComponentName activityComponent, @ActivityEventType int eventType);
+            @NonNull ComponentName activityComponent, @ActivityEventType int eventType,
+            @NonNull ActivityId activityId);
 }
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
new file mode 100644
index 0000000..87cbbfe
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 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.display;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.util.Objects;
+
+/**
+ * A state class representing a set of brightness related entities that are decided at runtime by
+ * the DisplayBrightnessModeStrategies when updating the brightness.
+ */
+public final class DisplayBrightnessState {
+    private final float mBrightness;
+    private final float mSdrBrightness;
+    private final BrightnessReason mBrightnessReason;
+
+    private DisplayBrightnessState(Builder builder) {
+        this.mBrightness = builder.getBrightness();
+        this.mSdrBrightness = builder.getSdrBrightness();
+        this.mBrightnessReason = builder.getBrightnessReason();
+    }
+
+    /**
+     * Gets the brightness
+     */
+    public float getBrightness() {
+        return mBrightness;
+    }
+
+    /**
+     * Gets the sdr brightness
+     */
+    public float getSdrBrightness() {
+        return mSdrBrightness;
+    }
+
+    /**
+     * Gets the {@link BrightnessReason}
+     */
+    public BrightnessReason getBrightnessReason() {
+        return mBrightnessReason;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
+        stringBuilder.append("\n    brightness:");
+        stringBuilder.append(getBrightness());
+        stringBuilder.append("\n    sdrBrightness:");
+        stringBuilder.append(getSdrBrightness());
+        stringBuilder.append("\n    brightnessReason:");
+        stringBuilder.append(getBrightnessReason());
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Checks whether the two objects have the same values.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+
+        if (!(other instanceof DisplayBrightnessState)) {
+            return false;
+        }
+
+        DisplayBrightnessState
+                displayBrightnessState = (DisplayBrightnessState) other;
+
+        if (mBrightness != displayBrightnessState.getBrightness()) {
+            return false;
+        }
+        if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
+            return false;
+        }
+        if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+    }
+
+    /**
+     * A DisplayBrightnessState's builder class.
+     */
+    public static class Builder {
+        private float mBrightness;
+        private float mSdrBrightness;
+        private BrightnessReason mBrightnessReason = new BrightnessReason();
+
+        /**
+         * Gets the brightness
+         */
+        public float getBrightness() {
+            return mBrightness;
+        }
+
+        /**
+         * Sets the brightness
+         *
+         * @param brightness The brightness to be associated with DisplayBrightnessState's
+         *                   builder
+         */
+        public Builder setBrightness(float brightness) {
+            this.mBrightness = brightness;
+            return this;
+        }
+
+        /**
+         * Gets the sdr brightness
+         */
+        public float getSdrBrightness() {
+            return mSdrBrightness;
+        }
+
+        /**
+         * Sets the sdr brightness
+         *
+         * @param sdrBrightness The sdr brightness to be associated with DisplayBrightnessState's
+         *                      builder
+         */
+        public Builder setSdrBrightness(float sdrBrightness) {
+            this.mSdrBrightness = sdrBrightness;
+            return this;
+        }
+
+        /**
+         * Gets the {@link BrightnessReason}
+         */
+        public BrightnessReason getBrightnessReason() {
+            return mBrightnessReason;
+        }
+
+        /**
+         * Sets the {@link BrightnessReason}
+         *
+         * @param brightnessReason The brightness reason {@link BrightnessReason} to be
+         *                         associated with the builder
+         */
+        public Builder setBrightnessReason(BrightnessReason brightnessReason) {
+            this.mBrightnessReason = brightnessReason;
+            return this;
+        }
+
+        /**
+         * This is used to construct an immutable DisplayBrightnessState object from its builder
+         */
+        public DisplayBrightnessState build() {
+            return new DisplayBrightnessState(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 81219ba..bf981be 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@
  *       <quirk>canSetBrightnessViaHwc</quirk>
  *      </quirks>
  *
- *      <autoBrightness>
+ *      <autoBrightness enable="true">
  *          <brighteningLightDebounceMillis>
  *              2000
  *          </brighteningLightDebounceMillis>
@@ -507,6 +507,11 @@
     private long mAutoBrightnessDarkeningLightDebounce =
             INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
 
+    // This setting allows non-default displays to have autobrightness enabled.
+    private boolean mAutoBrightnessAvailable = false;
+    // This stores the raw value loaded from the config file - true if not written.
+    private boolean mDdcAutoBrightnessAvailable = true;
+
     // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
     // data, which comes from the ddc, and the current one, which may be the DeviceConfig
     // overwritten value.
@@ -1119,6 +1124,10 @@
         return mProximitySensor;
     }
 
+    boolean isAutoBrightnessAvailable() {
+        return mAutoBrightnessAvailable;
+    }
+
     /**
      * @param quirkValue The quirk to test.
      * @return {@code true} if the specified quirk is present in this configuration, {@code false}
@@ -1271,6 +1280,8 @@
                 + mAutoBrightnessDarkeningLightDebounce
                 + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
                 + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+                + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+                + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "}";
     }
 
@@ -1349,6 +1360,7 @@
         loadBrightnessChangeThresholdsFromXml();
         setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
+        loadAutoBrightnessAvailableFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -1367,6 +1379,7 @@
         setSimpleMappingStrategyValues();
         loadAmbientLightSensorFromConfigXml();
         setProxSensorUnspecified();
+        loadAutoBrightnessAvailableFromConfigXml();
     }
 
     private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
@@ -1559,9 +1572,11 @@
     }
 
     private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
-        loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
-        loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
-        loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
+        final AutoBrightness autoBrightness = config.getAutoBrightness();
+        loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
+        loadAutoBrightnessDarkeningLightDebounce(autoBrightness);
+        loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+        loadEnableAutoBrightness(autoBrightness);
     }
 
     /**
@@ -1623,6 +1638,11 @@
         }
     }
 
+    private void loadAutoBrightnessAvailableFromConfigXml() {
+        mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+                R.bool.config_automatic_brightness_available);
+    }
+
     private void loadBrightnessMapFromConfigXml() {
         // Use the config.xml mapping
         final Resources res = mContext.getResources();
@@ -2262,6 +2282,20 @@
         return levels;
     }
 
+    private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
+        // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
+        // config.xml values if the autobrightness tag is not defined in the ddc file.
+        // Autobrightness can still be turned off globally via config_automatic_brightness_available
+        mDdcAutoBrightnessAvailable = true;
+        if (autobrightness != null) {
+            mDdcAutoBrightnessAvailable = autobrightness.getEnabled();
+        }
+
+        mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_automatic_brightness_available)
+                && mDdcAutoBrightnessAvailable;
+    }
+
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 4752044..422e98f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -566,13 +566,6 @@
         mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
 
-        // Check the setting, but also verify that it is the default display. Only the default
-        // display has an automatic brightness controller running.
-        // TODO: b/179021925 - Fix to work with multiple displays
-        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_automatic_brightness_available)
-                && mDisplayId == Display.DEFAULT_DISPLAY;
-
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
 
@@ -952,6 +945,8 @@
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
+        mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
         if (!mUseSoftwareAutoBrightnessConfig) {
             return;
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 172b4be..23c020e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -542,13 +542,6 @@
         mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
 
-        // Check the setting, but also verify that it is the default display. Only the default
-        // display has an automatic brightness controller running.
-        // TODO: b/179021925 - Fix to work with multiple displays
-        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
-                R.bool.config_automatic_brightness_available)
-                && mDisplayId == Display.DEFAULT_DISPLAY;
-
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
 
@@ -928,6 +921,8 @@
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
+        mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
         if (!mUseSoftwareAutoBrightnessConfig) {
             return;
         }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
new file mode 100644
index 0000000..3be5933
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.display.brightness.strategy;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * An interface to define the general skeleton of how a BrightnessModeStrategy should look like
+ * This is responsible for deciding the DisplayBrightnessState that the display should change to,
+ * not taking into account clamping that might be needed
+ */
+public interface DisplayBrightnessModeStrategy {
+    /**
+     * Decides the DisplayBrightnessState that the system should change to.
+     *
+     * @param displayPowerRequest           The request to evaluate the updated brightness
+     * @param displayState                  The target displayState to which the system should
+     *                                      change to after processing the request
+     * @param displayBrightnessStateBuilder The DisplayBrightnessStateBuilder, consisting of
+     *                                      DisplayBrightnessState that have been constructed so far
+     */
+    DisplayBrightnessState.Builder updateBrightness(
+            DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int displayState,
+            DisplayBrightnessState.Builder displayBrightnessStateBuilder);
+
+    /**
+     * Used to dump the state.
+     *
+     * @param writer The PrintWriter used to dump the state.
+     */
+    void dump(PrintWriter writer);
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 696b604..324eefc 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -38,7 +38,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.BatteryController.UEventManager.UEventListener;
+import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -438,7 +438,7 @@
         private State mState;
 
         @Nullable
-        private UEventListener mUEventListener;
+        private UEventBatteryListener mUEventBatteryListener;
 
         DeviceMonitor(int deviceId) {
             mState = new State(deviceId);
@@ -473,20 +473,26 @@
                 return;
             }
             final int deviceId = mState.deviceId;
-            mUEventListener = new UEventListener() {
+            mUEventBatteryListener = new UEventBatteryListener() {
                 @Override
-                public void onUEvent(long eventTime) {
+                public void onBatteryUEvent(long eventTime) {
                     handleUEventNotification(deviceId, eventTime);
                 }
             };
-            mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
+            mUEventManager.addListener(
+                    mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath));
+        }
+
+        private String formatDevPath(String path) {
+            // Remove the "/sys" prefix if it has one.
+            return path.startsWith("/sys") ? path.substring(4) : path;
         }
 
         // This must be called when the device is no longer being monitored.
         public void stopMonitoring() {
-            if (mUEventListener != null) {
-                mUEventManager.removeListener(mUEventListener);
-                mUEventListener = null;
+            if (mUEventBatteryListener != null) {
+                mUEventManager.removeListener(mUEventBatteryListener);
+                mUEventBatteryListener = null;
             }
         }
 
@@ -498,7 +504,7 @@
         @Override
         public String toString() {
             return "state=" + mState
-                    + ", uEventListener=" + (mUEventListener != null ? "added" : "none");
+                    + ", uEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
         }
     }
 
@@ -507,7 +513,7 @@
     interface UEventManager {
 
         @VisibleForTesting
-        abstract class UEventListener {
+        abstract class UEventBatteryListener {
             private final UEventObserver mObserver = new UEventObserver() {
                 @Override
                 public void onUEvent(UEvent event) {
@@ -517,18 +523,23 @@
                                 "UEventListener: Received UEvent: "
                                         + event + " eventTime: " + eventTime);
                     }
-                    UEventListener.this.onUEvent(eventTime);
+                    if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
+                            || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
+                        // Disregard any UEvents that do not correspond to battery changes.
+                        return;
+                    }
+                    UEventBatteryListener.this.onBatteryUEvent(eventTime);
                 }
             };
 
-            public abstract void onUEvent(long eventTime);
+            public abstract void onBatteryUEvent(long eventTime);
         }
 
-        default void addListener(UEventListener listener, String match) {
+        default void addListener(UEventBatteryListener listener, String match) {
             listener.mObserver.startObserving(match);
         }
 
-        default void removeListener(UEventListener listener) {
+        default void removeListener(UEventBatteryListener listener) {
             listener.mObserver.stopObserving();
         }
     }
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 66bdadb..ab69c34 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -333,16 +333,25 @@
         byte scryptLogP;
         public int credentialType;
         byte[] salt;
-        // If Weaver is available, then this field is empty.  Otherwise, it is the Gatekeeper
-        // password handle that resulted from enrolling the hashed LSKF.
+        // This is the Gatekeeper password handle that resulted from enrolling the stretched LSKF,
+        // when applicable.  This field isn't used if Weaver is available, or in new protectors when
+        // the LSKF is empty.
         public byte[] passwordHandle;
 
-        public static PasswordData create(int passwordType) {
+        public static PasswordData create(int credentialType) {
             PasswordData result = new PasswordData();
-            result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
-            result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
-            result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
-            result.credentialType = passwordType;
+            if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+                // When the LSKF is empty, scrypt provides no security benefit, so just use the
+                // minimum parameters (N=2, r=1, p=1).
+                result.scryptLogN = 1;
+                result.scryptLogR = 0;
+                result.scryptLogP = 0;
+            } else {
+                result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
+                result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
+                result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
+            }
+            result.credentialType = credentialType;
             result.salt = secureRandom(PASSWORD_SALT_LENGTH);
             return result;
         }
@@ -776,11 +785,12 @@
         long protectorId = generateProtectorId();
         PasswordData pwd = PasswordData.create(credential.getType());
         byte[] stretchedLskf = stretchLskf(credential, pwd);
-        final long sid;
+        long sid = GateKeeper.INVALID_SECURE_USER_ID;
         final byte[] protectorSecret;
 
         if (isWeaverAvailable()) {
-            // Protector uses Weaver to verify the LSKF
+            // Weaver is available, so make the protector use it to verify the LSKF.  Do this even
+            // if the LSKF is empty, as that gives us support for securely deleting the protector.
             int weaverSlot = getNextAvailableWeaverSlot();
             Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
             byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf),
@@ -794,33 +804,34 @@
             // No need to pass in quality since the credential type already encodes sufficient info
             synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot);
 
-            pwd.passwordHandle = null;
-            sid = GateKeeper.INVALID_SECURE_USER_ID;
             protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
         } else {
-            // Protector uses Gatekeeper to verify the LSKF
-
-            // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
-            // to prevent them from accumulating and causing problems.
-            try {
-                gatekeeper.clearSecureUserId(fakeUserId(userId));
-            } catch (RemoteException ignore) {
-                Slog.w(TAG, "Failed to clear SID from gatekeeper");
+            // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
+            // instead.  However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
+            // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
+            if (!credential.isNone()) {
+                // In case GK enrollment leaves persistent state around (in RPMB), this will nuke
+                // them to prevent them from accumulating and causing problems.
+                try {
+                    gatekeeper.clearSecureUserId(fakeUserId(userId));
+                } catch (RemoteException ignore) {
+                    Slog.w(TAG, "Failed to clear SID from gatekeeper");
+                }
+                GateKeeperResponse response;
+                try {
+                    response = gatekeeper.enroll(fakeUserId(userId), null, null,
+                            stretchedLskfToGkPassword(stretchedLskf));
+                } catch (RemoteException e) {
+                    throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
+                            + " for user " + userId, e);
+                }
+                if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+                    throw new IllegalStateException("Failed to enroll LSKF for new SP protector"
+                            + " for user " + userId);
+                }
+                pwd.passwordHandle = response.getPayload();
+                sid = sidFromPasswordHandle(pwd.passwordHandle);
             }
-            GateKeeperResponse response;
-            try {
-                response = gatekeeper.enroll(fakeUserId(userId), null, null,
-                        stretchedLskfToGkPassword(stretchedLskf));
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
-                        + "user " + userId, e);
-            }
-            if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
-                throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
-                        + "user " + userId);
-            }
-            pwd.passwordHandle = response.getPayload();
-            sid = sidFromPasswordHandle(pwd.passwordHandle);
             protectorSecret = transformUnderSecdiscardable(stretchedLskf,
                     createSecdiscardable(protectorId, userId));
             // No need to pass in quality since the credential type already encodes sufficient info
@@ -1049,7 +1060,7 @@
         byte[] stretchedLskf = stretchLskf(credential, pwd);
 
         final byte[] protectorSecret;
-        final long sid;
+        long sid = GateKeeper.INVALID_SECURE_USER_ID;
         int weaverSlot = loadWeaverSlot(protectorId, userId);
         if (weaverSlot != INVALID_WEAVER_SLOT) {
             // Protector uses Weaver to verify the LSKF
@@ -1062,54 +1073,62 @@
             if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                 return result;
             }
-            sid = GateKeeper.INVALID_SECURE_USER_ID;
             protectorSecret = transformUnderWeaverSecret(stretchedLskf,
                     result.gkResponse.getGatekeeperHAT());
         } else {
-            // Protector uses Gatekeeper to verify the LSKF
-            byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
-            GateKeeperResponse response;
-            try {
-                response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
-                        pwd.passwordHandle, gkPassword);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "gatekeeper verify failed", e);
-                result.gkResponse = VerifyCredentialResponse.ERROR;
-                return result;
-            }
-            int responseCode = response.getResponseCode();
-            if (responseCode == GateKeeperResponse.RESPONSE_OK) {
-                result.gkResponse = VerifyCredentialResponse.OK;
-                if (response.getShouldReEnroll()) {
-                    GateKeeperResponse reenrollResponse;
-                    try {
-                        reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
-                                pwd.passwordHandle, gkPassword, gkPassword);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
-                        reenrollResponse = GateKeeperResponse.ERROR;
-                        // continue the flow anyway
-                    }
-                    if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
-                        pwd.passwordHandle = reenrollResponse.getPayload();
-                        // Use the reenrollment opportunity to update credential type
-                        // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
-                        pwd.credentialType = credential.getType();
-                        saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
-                        synchronizeFrpPassword(pwd, 0, userId);
-                    } else {
-                        Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
-                        // continue the flow anyway
-                    }
+            // Weaver is unavailable, so the protector uses Gatekeeper to verify the LSKF, unless
+            // the LSKF is empty in which case Gatekeeper might not have been used at all.
+            if (pwd.passwordHandle == null) {
+                if (!credential.isNone()) {
+                    Slog.e(TAG, "Missing Gatekeeper password handle for nonempty LSKF");
+                    result.gkResponse = VerifyCredentialResponse.ERROR;
+                    return result;
                 }
-            } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
-                result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
-                return result;
-            } else  {
-                result.gkResponse = VerifyCredentialResponse.ERROR;
-                return result;
+            } else {
+                byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
+                GateKeeperResponse response;
+                try {
+                    response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
+                            pwd.passwordHandle, gkPassword);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "gatekeeper verify failed", e);
+                    result.gkResponse = VerifyCredentialResponse.ERROR;
+                    return result;
+                }
+                int responseCode = response.getResponseCode();
+                if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+                    result.gkResponse = VerifyCredentialResponse.OK;
+                    if (response.getShouldReEnroll()) {
+                        GateKeeperResponse reenrollResponse;
+                        try {
+                            reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
+                                    pwd.passwordHandle, gkPassword, gkPassword);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
+                            reenrollResponse = GateKeeperResponse.ERROR;
+                            // continue the flow anyway
+                        }
+                        if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+                            pwd.passwordHandle = reenrollResponse.getPayload();
+                            // Use the reenrollment opportunity to update credential type
+                            // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
+                            pwd.credentialType = credential.getType();
+                            saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+                            synchronizeFrpPassword(pwd, 0, userId);
+                        } else {
+                            Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
+                            // continue the flow anyway
+                        }
+                    }
+                } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+                    result.gkResponse = VerifyCredentialResponse.fromTimeout(response.getTimeout());
+                    return result;
+                } else  {
+                    result.gkResponse = VerifyCredentialResponse.ERROR;
+                    return result;
+                }
+                sid = sidFromPasswordHandle(pwd.passwordHandle);
             }
-            sid = sidFromPasswordHandle(pwd.passwordHandle);
             protectorSecret = transformUnderSecdiscardable(stretchedLskf,
                     loadSecdiscardable(protectorId, userId));
         }
@@ -1463,7 +1482,8 @@
         return result;
     }
 
-    private int fakeUserId(int userId) {
+    @VisibleForTesting
+    static int fakeUserId(int userId) {
         return 100000 + userId;
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4b18add..77fea09 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4872,6 +4872,13 @@
         }
 
         @Override
+        public int getHintsFromListenerNoToken() {
+            synchronized (mNotificationLock) {
+                return mListenerHints;
+            }
+        }
+
+        @Override
         public void requestInterruptionFilterFromListener(INotificationListener token,
                 int interruptionFilter) throws RemoteException {
             final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index d2a5e32..3385a09 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -26,6 +26,7 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.incremental.PerUidReadTimeouts;
 import android.service.pm.PackageServiceDumpProto;
@@ -60,6 +61,7 @@
     private final ArrayMap<String, FeatureInfo> mAvailableFeatures;
     private final ArraySet<String> mProtectedBroadcasts;
     private final PerUidReadTimeouts[] mPerUidReadTimeouts;
+    private final SnapshotStatistics mSnapshotStatistics;
 
     DumpHelper(
             PermissionManagerServiceInternal permissionManager,
@@ -70,7 +72,8 @@
             ChangedPackagesTracker changedPackagesTracker,
             ArrayMap<String, FeatureInfo> availableFeatures,
             ArraySet<String> protectedBroadcasts,
-            PerUidReadTimeouts[] perUidReadTimeouts) {
+            PerUidReadTimeouts[] perUidReadTimeouts,
+            SnapshotStatistics snapshotStatistics) {
         mPermissionManager = permissionManager;
         mStorageEventHelper = storageEventHelper;
         mDomainVerificationManager = domainVerificationManager;
@@ -81,6 +84,7 @@
         mAvailableFeatures = availableFeatures;
         mProtectedBroadcasts = protectedBroadcasts;
         mPerUidReadTimeouts = perUidReadTimeouts;
+        mSnapshotStatistics = snapshotStatistics;
     }
 
     @NeverCompile // Avoid size overhead of debugging code.
@@ -585,6 +589,9 @@
             if (dumpState.onTitlePrinted()) {
                 pw.println();
             }
+            pw.println("Snapshot statistics:");
+            mSnapshotStatistics.dump(pw, "   " /* indent */, SystemClock.currentTimeMicro(),
+                    snapshot.getUsed(), dumpState.isBrief());
         }
 
         if (!checkin
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e9f26e9..022bf3c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -81,9 +81,11 @@
 import android.content.pm.InstallationFileParcel;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.PreapprovalDetails;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningDetails;
 import android.content.pm.dex.DexMetadataHelper;
@@ -92,8 +94,13 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.icu.util.ULocale;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -193,6 +200,7 @@
     private static final int MSG_INSTALL = 3;
     private static final int MSG_ON_PACKAGE_INSTALLED = 4;
     private static final int MSG_SESSION_VALIDATION_FAILURE = 5;
+    private static final int MSG_PRE_APPROVAL_REQUEST = 6;
 
     /** XML constants used for persisting a session */
     static final String TAG_SESSION = "session";
@@ -275,7 +283,7 @@
      * #mValidatedTargetSdk} is compared with {@link Build.VERSION_CODES#R} before getting the
      * target sdk version from a validated apk in {@link #validateApkInstallLocked()}, the compared
      * result will not trigger any user action in
-     * {@link #checkUserActionRequirement(PackageInstallerSession)}.
+     * {@link #checkUserActionRequirement(PackageInstallerSession, IntentSender)}.
      */
     private static final int INVALID_TARGET_SDK_VERSION = Integer.MAX_VALUE;
 
@@ -360,6 +368,7 @@
     @GuardedBy("mLock")
     private boolean mShouldBeSealed = false;
 
+    private final AtomicBoolean mPreapprovalRequested = new AtomicBoolean(false);
     private final AtomicBoolean mCommitted = new AtomicBoolean(false);
 
     /**
@@ -387,6 +396,9 @@
     @GuardedBy("mLock")
     private IntentSender mRemoteStatusReceiver;
 
+    @GuardedBy("mLock")
+    private PreapprovalDetails mPreapprovalDetails;
+
     /** Fields derived from commit parsing */
     @GuardedBy("mLock")
     private String mPackageName;
@@ -740,11 +752,12 @@
                     final Bundle extras = (Bundle) args.arg3;
                     final IntentSender statusReceiver = (IntentSender) args.arg4;
                     final int returnCode = args.argi1;
+                    final boolean isPreapproval = args.argi2 == 1;
                     args.recycle();
 
                     sendOnPackageInstalled(mContext, statusReceiver, sessionId,
                             isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,
-                            packageName, returnCode, message, extras);
+                            packageName, returnCode, isPreapproval, message, extras);
 
                     break;
                 case MSG_SESSION_VALIDATION_FAILURE:
@@ -752,6 +765,9 @@
                     final String detailMessage = (String) msg.obj;
                     onSessionValidationFailure(error, detailMessage);
                     break;
+                case MSG_PRE_APPROVAL_REQUEST:
+                    handlePreapprovalRequest();
+                    break;
             }
 
             return true;
@@ -779,10 +795,7 @@
      */
     private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwner() {
         assertNotLocked("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
-        // It is safe to access mInstallerUid and mInstallSource without lock
-        // because they are immutable after sealing.
-        assertSealed("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
-        if (userId != UserHandle.getUserId(mInstallerUid)) {
+        if (userId != UserHandle.getUserId(getInstallerUid())) {
             return false;
         }
         DevicePolicyManagerInternal dpmi =
@@ -874,7 +887,7 @@
 
         if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
                 userId)) {
-            // show the installer to account for device poslicy or unknown sources use cases
+            // show the installer to account for device policy or unknown sources use cases
             return USER_ACTION_REQUIRED;
         }
 
@@ -1031,18 +1044,22 @@
                     mResolvedBaseFile.getAbsolutePath() : null;
             info.progress = progress;
             info.sealed = mSealed;
-            info.isCommitted = mCommitted.get();
+            info.isCommitted = isCommitted();
+            info.isPreapprovalRequested = isPreapprovalRequested();
             info.active = mActiveCount.get() > 0;
 
             info.mode = params.mode;
             info.installReason = params.installReason;
             info.installScenario = params.installScenario;
             info.sizeBytes = params.sizeBytes;
-            info.appPackageName = mPackageName != null ? mPackageName : params.appPackageName;
+            info.appPackageName = mPreapprovalDetails != null ? mPreapprovalDetails.getPackageName()
+                    : mPackageName != null ? mPackageName : params.appPackageName;
             if (includeIcon) {
-                info.appIcon = params.appIcon;
+                info.appIcon = mPreapprovalDetails != null && mPreapprovalDetails.getIcon() != null
+                        ? mPreapprovalDetails.getIcon() : params.appIcon;
             }
-            info.appLabel = params.appLabel;
+            info.appLabel =
+                    mPreapprovalDetails != null ? mPreapprovalDetails.getLabel() : params.appLabel;
 
             info.installLocation = params.installLocation;
             if (!scrubData) {
@@ -1086,6 +1103,11 @@
         }
     }
 
+    /** @hide */
+    boolean isPreapprovalRequested() {
+        return mPreapprovalRequested.get();
+    }
+
     /** {@hide} */
     boolean isCommitted() {
         return mCommitted.get();
@@ -1122,6 +1144,14 @@
     }
 
     @GuardedBy("mLock")
+    private void assertPreparedAndNotPreapprovalRequestedLocked(String cookie) {
+        assertPreparedAndNotSealedLocked(cookie);
+        if (isPreapprovalRequested()) {
+            throw new IllegalStateException(cookie + " not allowed after requesting");
+        }
+    }
+
+    @GuardedBy("mLock")
     private void assertPreparedAndNotSealedLocked(String cookie) {
         assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
         if (mSealed) {
@@ -1132,7 +1162,7 @@
     @GuardedBy("mLock")
     private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
         assertPreparedAndNotDestroyedLocked(cookie);
-        if (mCommitted.get()) {
+        if (isCommitted()) {
             throw new SecurityException(cookie + " not allowed after commit");
         }
     }
@@ -1173,7 +1203,7 @@
 
     @GuardedBy("mProgressLock")
     private void computeProgressLocked(boolean forcePublish) {
-        if (!isIncrementalInstallation() || !mCommitted.get()) {
+        if (!isIncrementalInstallation() || !isCommitted()) {
             mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
                     + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
         } else {
@@ -1200,7 +1230,7 @@
         assertCallerIsOwnerRootOrVerifier();
         synchronized (mLock) {
             assertPreparedAndNotDestroyedLocked("getNames");
-            if (!mCommitted.get()) {
+            if (!isCommitted()) {
                 return getNamesLocked();
             } else {
                 return getStageDirContentsLocked();
@@ -1640,11 +1670,7 @@
 
     @Override
     public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
-        if (hasParentSessionId()) {
-            throw new IllegalStateException(
-                    "Session " + sessionId + " is a child of multi-package session "
-                            + getParentSessionId() +  " and may not be committed directly.");
-        }
+        assertNotChild("commit");
 
         if (!markAsSealed(statusReceiver, forTransfer)) {
             return;
@@ -1708,6 +1734,20 @@
         }
     }
 
+    @WorkerThread
+    private void handlePreapprovalRequest() {
+        /**
+         * Stops the process if the session needs user action. When the user answers the yes,
+         * {@link #setPermissionsResult(boolean)} is called and then
+         * {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
+         */
+        if (sendPendingUserActionIntentIfNeeded()) {
+            return;
+        }
+
+        dispatchSessionPreappoved();
+    }
+
     private final class FileSystemConnector extends
             IPackageInstallerSessionFileSystemConnector.Stub {
         final Set<String> mAddedFiles = new ArraySet<>();
@@ -1835,7 +1875,7 @@
         //             single / child sessions.
         try {
             synchronized (mLock) {
-                if (mCommitted.get()) {
+                if (isCommitted()) {
                     return true;
                 }
                 // Read transfers from the original owner stay open, but as the session's data
@@ -2115,7 +2155,11 @@
      */
     @WorkerThread
     private boolean sendPendingUserActionIntentIfNeeded() {
-        assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
+        // To support pre-approval request of atomic install, we allow child session to handle
+        // the result by itself since it has the status receiver.
+        if (isCommitted()) {
+            assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
+        }
 
         final IntentSender statusReceiver = getRemoteStatusReceiver();
         return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
@@ -2420,7 +2464,10 @@
         // User needs to confirm installation;
         // give installer an intent they can use to involve
         // user.
-        final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
+        final boolean isPreapproval = isPreapprovalRequested() && !isCommitted();
+        final Intent intent = new Intent(
+                isPreapproval ? PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL
+                        : PackageInstaller.ACTION_CONFIRM_INSTALL);
         intent.setPackage(mPm.getPackageInstallerPackageName());
         intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
 
@@ -3009,6 +3056,9 @@
                 }
             }
         }
+
+        assertPreapprovalDetailsConsistentIfNeededLocked(packageLite, pkgInfo);
+
         if (packageLite.isUseEmbeddedDex()) {
             for (File file : mResolvedStagedFiles) {
                 if (file.getName().endsWith(".apk")
@@ -3253,6 +3303,78 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void assertPreapprovalDetailsConsistentIfNeededLocked(@NonNull PackageLite packageLite,
+            @Nullable PackageInfo info) throws PackageManagerException {
+        if (mPreapprovalDetails == null || !isPreapprovalRequested()) {
+            return;
+        }
+
+        if (!TextUtils.equals(mPackageName, mPreapprovalDetails.getPackageName())) {
+            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                    mPreapprovalDetails + " inconsistent with " + mPackageName);
+        }
+
+        // In case the app label in PreapprovalDetails from different locale in split APK,
+        // we check all APK files to find the app label.
+        final PackageInfo packageInfo =
+                info != null ? info : mContext.getPackageManager().getPackageArchiveInfo(
+                        packageLite.getPath(), PackageInfoFlags.of(0));
+        if (packageInfo == null) {
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Failure to obtain package info.");
+        }
+        final List<String> filePaths = packageLite.getAllApkPaths();
+        final String appLabel = mPreapprovalDetails.getLabel();
+        final ULocale appLocale = mPreapprovalDetails.getLocale();
+        final ApplicationInfo appInfo = packageInfo.applicationInfo;
+        boolean appLabelMatched = false;
+        for (int i = filePaths.size() - 1; i >= 0 && !appLabelMatched; i--) {
+            appLabelMatched |= TextUtils.equals(getAppLabel(filePaths.get(i), appLocale, appInfo),
+                    appLabel);
+        }
+        if (!appLabelMatched) {
+            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                    mPreapprovalDetails + " inconsistent with app label");
+        }
+    }
+
+    private CharSequence getAppLabel(String path, ULocale locale, ApplicationInfo appInfo)
+            throws PackageManagerException {
+        final Resources pRes = mContext.getResources();
+        final AssetManager assetManager = new AssetManager();
+        final Configuration config = new Configuration(pRes.getConfiguration());
+        final ApkAssets apkAssets;
+        try {
+            apkAssets = ApkAssets.loadFromPath(path);
+        } catch (IOException e) {
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Failure to get resources from package archive " + path);
+        }
+        assetManager.setApkAssets(new ApkAssets[]{apkAssets}, false /* invalidateCaches */);
+        config.setLocale(locale.toLocale());
+        final Resources res = new Resources(assetManager, pRes.getDisplayMetrics(), config);
+        return tryLoadingAppLabel(res, appInfo);
+    }
+
+    private CharSequence tryLoadingAppLabel(@NonNull Resources res, @NonNull ApplicationInfo info) {
+        CharSequence label = null;
+        // Try to load the label from the package's resources. If an app has not explicitly
+        // specified any label, just use the package name.
+        if (info.labelRes != 0) {
+            try {
+                label = res.getText(info.labelRes);
+            } catch (Resources.NotFoundException ignore) {
+            }
+        }
+        if (label == null) {
+            label = (info.nonLocalizedLabel != null)
+                    ? info.nonLocalizedLabel : info.packageName;
+        }
+
+        return label;
+    }
+
     private SigningDetails unsafeGetCertsWithoutVerification(String path)
             throws PackageManagerException {
         final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
@@ -3469,11 +3591,13 @@
     }
 
     void setPermissionsResult(boolean accepted) {
-        if (!isSealed()) {
+        if (!isSealed() && !isPreapprovalRequested()) {
             throw new SecurityException("Must be sealed to accept permissions");
         }
 
-        PackageInstallerSession root = hasParentSessionId()
+        // To support pre-approval request of atomic install, we allow child session to handle
+        // the result by itself since it has the status receiver.
+        final PackageInstallerSession root = hasParentSessionId() && isCommitted()
                 ? mSessionProvider.getSession(getParentSessionId()) : this;
 
         if (accepted) {
@@ -3481,7 +3605,8 @@
             synchronized (mLock) {
                 mPermissionsManuallyAccepted = true;
             }
-            root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
+            root.mHandler.obtainMessage(
+                    isCommitted() ? MSG_INSTALL : MSG_PRE_APPROVAL_REQUEST).sendToTarget();
         } else {
             root.destroy();
             root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
@@ -3593,7 +3718,7 @@
             mDestroyed = true;
             r = () -> {
                 assertNotLocked("abandonStaged");
-                if (isStaged() && mCommitted.get()) {
+                if (isStaged() && isCommitted()) {
                     mStagingManager.abortCommittedSession(mStagedSession);
                 }
                 destroy();
@@ -3937,7 +4062,7 @@
     private boolean canBeAddedAsChild(int parentCandidate) {
         synchronized (mLock) {
             return (!hasParentSessionId() || mParentSessionId == parentCandidate)
-                    && !mCommitted.get()
+                    && !isCommitted()
                     && !mDestroyed;
         }
     }
@@ -4096,10 +4221,68 @@
             args.arg3 = extras;
             args.arg4 = statusReceiver;
             args.argi1 = returnCode;
+            args.argi2 = isPreapprovalRequested() && !isCommitted() ? 1 : 0;
             mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
         }
     }
 
+    private void dispatchSessionPreappoved() {
+        final IntentSender target = getRemoteStatusReceiver();
+        final Intent intent = new Intent();
+        intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+        intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
+        intent.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, true);
+        try {
+            target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
+                    null /* handler */);
+        } catch (IntentSender.SendIntentException ignored) {
+        }
+    }
+
+    @Override
+    public void requestUserPreapproval(@NonNull PreapprovalDetails details,
+            @NonNull IntentSender statusReceiver) {
+        validatePreapprovalRequest(details, statusReceiver);
+        dispatchPreapprovalRequest();
+    }
+
+    /**
+     * Validates whether the necessary information (e.g., PreapprovalDetails) are provided.
+     */
+    private void validatePreapprovalRequest(@NonNull PreapprovalDetails details,
+            @NonNull IntentSender statusReceiver) {
+        assertCallerIsOwnerOrRoot();
+        if (isMultiPackage()) {
+            throw new IllegalStateException(
+                    "Session " + sessionId + " is a parent of multi-package session and "
+                            + "requestUserPreapproval on the parent session isn't supported.");
+        }
+
+        synchronized (mLock) {
+            assertPreparedAndNotSealedLocked("request of session " + sessionId);
+            mPreapprovalDetails = details;
+            setRemoteStatusReceiver(statusReceiver);
+        }
+    }
+
+    private void dispatchPreapprovalRequest() {
+        synchronized (mLock) {
+            assertPreparedAndNotPreapprovalRequestedLocked("dispatchPreapprovalRequest");
+        }
+
+        // Mark this session are pre-approval requested, and ready to progress to the next phase.
+        markAsPreapprovalRequested();
+
+        mHandler.obtainMessage(MSG_PRE_APPROVAL_REQUEST).sendToTarget();
+    }
+
+    /**
+     * Marks this session as pre-approval requested, and prevents further related modification.
+     */
+    private void markAsPreapprovalRequested() {
+        mPreapprovalRequested.set(true);
+    }
+
     void setSessionReady() {
         synchronized (mLock) {
             // Do not allow destroyed/failed session to change state
@@ -4265,6 +4448,7 @@
         pw.printPair("mClientProgress", clientProgress);
         pw.printPair("mProgress", progress);
         pw.printPair("mCommitted", mCommitted);
+        pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
         pw.printPair("mSealed", mSealed);
         pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
         pw.printPair("mStageDirInUse", mStageDirInUse);
@@ -4282,6 +4466,7 @@
         pw.printPair("mSessionReady", mSessionReady);
         pw.printPair("mSessionErrorCode", mSessionErrorCode);
         pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
+        pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
         pw.println();
 
         pw.decreaseIndent();
@@ -4295,6 +4480,8 @@
         final Intent fillIn = new Intent();
         fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
         fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
+        fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL,
+                PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()));
         fillIn.putExtra(Intent.EXTRA_INTENT, intent);
         try {
             target.sendIntent(context, 0, fillIn, null, null);
@@ -4307,7 +4494,7 @@
      */
     private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,
             boolean showNotification, int userId, String basePackageName, int returnCode,
-            String msg, Bundle extras) {
+            boolean isPreapproval, String msg, Bundle extras) {
         if (INSTALL_SUCCEEDED == returnCode && showNotification) {
             boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
             Notification notification = PackageInstallerService.buildSuccessNotification(context,
@@ -4330,6 +4517,7 @@
         fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
                 PackageManager.installStatusToString(returnCode, msg));
         fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
+        fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, isPreapproval);
         if (extras != null) {
             final String existing = extras.getString(
                     PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
@@ -4448,7 +4636,7 @@
                 writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
             }
             writeBooleanAttribute(out, ATTR_PREPARED, mPrepared);
-            writeBooleanAttribute(out, ATTR_COMMITTED, mCommitted.get());
+            writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted());
             writeBooleanAttribute(out, ATTR_DESTROYED, mDestroyed);
             writeBooleanAttribute(out, ATTR_SEALED, mSealed);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d939ca6..8fed153 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6031,7 +6031,7 @@
             new DumpHelper(mPermissionManager, mStorageEventHelper,
                     mDomainVerificationManager, mInstallerService, mRequiredVerifierPackages,
                     knownPackages, mChangedPackagesTracker, availableFeatures, protectedBroadcasts,
-                    getPerUidReadTimeouts(snapshot)
+                    getPerUidReadTimeouts(snapshot), mSnapshotStatistics
             ).doDump(snapshot, fd, pw, args);
         }
     }
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index 95f80de..2cfc894 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -240,11 +240,6 @@
         public int mTotalUsed = 0;
 
         /**
-         * The total number of times a snapshot was bypassed because corking was in effect.
-         */
-        public int mTotalCorked = 0;
-
-        /**
          * The total number of builds that count as big, which means they took longer than
          * SNAPSHOT_BIG_BUILD_TIME_NS.
          */
@@ -297,13 +292,6 @@
             }
         }
 
-        /**
-         * Record a cork.
-         */
-        private void corked() {
-            mTotalCorked++;
-        }
-
         private Stats(long now) {
             mStartTimeUs = now;
             mTimes = new int[mTimeBins.count()];
@@ -321,7 +309,6 @@
             mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
             mTotalBuilds = orig.mTotalBuilds;
             mTotalUsed = orig.mTotalUsed;
-            mTotalCorked = orig.mTotalCorked;
             mBigBuilds = orig.mBigBuilds;
             mShortLived = orig.mShortLived;
             mTotalTimeUs = orig.mTotalTimeUs;
@@ -379,7 +366,6 @@
          * Dump the summary statistics record.  Choose the header or the data.
          *    number of builds
          *    number of uses
-         *    number of corks
          *    number of big builds
          *    number of short lifetimes
          *    cumulative build time, in seconds
@@ -388,13 +374,13 @@
         private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
             dumpPrefix(pw, indent, now, header, "Summary stats");
             if (header) {
-                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s  %10s",
-                          "TotBlds", "TotUsed", "TotCork", "BigBlds", "ShortLvd",
+                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s",
+                          "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
                           "TotTime", "MaxTime");
             } else {
                 pw.format(Locale.US,
-                        "  %10d  %10d  %10d  %10d  %10d  %10d  %10d",
-                        mTotalBuilds, mTotalUsed, mTotalCorked, mBigBuilds, mShortLived,
+                        "  %10d  %10d  %10d  %10d  %10d  %10d",
+                        mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
                         mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
             }
             pw.println();
@@ -559,16 +545,6 @@
     }
 
     /**
-     * Record a corked snapshot request.
-     */
-    public final void corked() {
-        synchronized (mLock) {
-            mShort[0].corked();
-            mLong[0].corked();
-        }
-    }
-
-    /**
      * Roll a stats array.  Shift the elements up an index and create a new element at
      * index zero.  The old element zero is completed with the specified time.
      */
@@ -624,8 +600,7 @@
      * Dump the statistics.  The format is compatible with the PackageManager dumpsys
      * output.
      */
-    public void dump(PrintWriter pw, String indent, long now, int unrecorded,
-                     int corkLevel, boolean brief) {
+    public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean brief) {
         // Grab the raw statistics under lock, but print them outside of the lock.
         Stats[] l;
         Stats[] s;
@@ -635,8 +610,7 @@
             s = Arrays.copyOf(mShort, mShort.length);
             s[0] = new Stats(s[0]);
         }
-        pw.format(Locale.US, "%s Unrecorded-hits: %d  Cork-level: %d", indent,
-                  unrecorded, corkLevel);
+        pw.format(Locale.US, "%s Unrecorded-hits: %d", indent, unrecorded);
         pw.println();
         dump(pw, indent, now, l, s, "stats");
         if (brief) {
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
new file mode 100644
index 0000000..5f76fbc
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2022 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.power.stats;
+
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+import android.util.TimeSparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IntPair;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Stores stats about CPU wakeups and tries to attribute them to subsystems and uids.
+ */
+public class CpuWakeupStats {
+    private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
+    @VisibleForTesting
+    static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
+    @VisibleForTesting
+    static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+
+    private final IrqDeviceMap mIrqDeviceMap;
+    private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
+
+    @VisibleForTesting
+    final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
+    @VisibleForTesting
+    final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
+            new TimeSparseArray<>();
+
+    public CpuWakeupStats(Context context, int mapRes) {
+        mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+    }
+
+    /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
+    public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
+            String rawReason) {
+        final Wakeup parsedWakeup = new Wakeup(rawReason, elapsedRealtime, uptime);
+        mWakeupEvents.put(elapsedRealtime, parsedWakeup);
+        attemptAttributionFor(parsedWakeup);
+        // Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order,
+        // we can delete all history that will not be useful in attributing future wakeups.
+        mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
+
+        // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that
+        // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
+        // had occurred before WAKEUP_RETENTION_MS.
+        int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+        for (int i = lastIdx; i >= 0; i--) {
+            mWakeupEvents.removeAt(i);
+        }
+        lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+        for (int i = lastIdx; i >= 0; i--) {
+            mWakeupAttribution.removeAt(i);
+        }
+    }
+
+    /** Notes a waking activity that could have potentially woken up the CPU. */
+    public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) {
+        if (!attemptAttributionWith(subsystem, elapsedRealtime, uids)) {
+            mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, uids);
+        }
+    }
+
+    private synchronized void attemptAttributionFor(Wakeup wakeup) {
+        final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
+        if (subsystems == null) {
+            // We don't support attribution for this kind of wakeup yet.
+            return;
+        }
+
+        SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
+        if (attribution == null) {
+            attribution = new SparseArray<>();
+            mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
+        }
+
+        for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) {
+            final int subsystem = subsystems.keyAt(subsystemIdx);
+
+            // Blame all activity that happened WAKEUP_REASON_HALF_WINDOW_MS before or after
+            // the wakeup from each responsible subsystem.
+            final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
+            final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
+
+            final SparseBooleanArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
+                    startTime, endTime);
+            attribution.put(subsystem, uidsToBlame);
+        }
+    }
+
+    private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
+            int... uids) {
+        final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
+                activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
+        final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
+                activityElapsed + WAKEUP_REASON_HALF_WINDOW_MS);
+
+        for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
+            final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx);
+            final SparseBooleanArray subsystems = getResponsibleSubsystemsForWakeup(wakeup);
+            if (subsystems == null) {
+                // Unsupported for attribution
+                continue;
+            }
+            if (subsystems.get(subsystem)) {
+                // We don't expect more than one wakeup to be found within such a short window, so
+                // just attribute this one and exit
+                SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+                        wakeup.mElapsedMillis);
+                if (attribution == null) {
+                    attribution = new SparseArray<>();
+                    mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
+                }
+                SparseBooleanArray uidsToBlame = attribution.get(subsystem);
+                if (uidsToBlame == null) {
+                    uidsToBlame = new SparseBooleanArray(uids.length);
+                    attribution.put(subsystem, uidsToBlame);
+                }
+                for (final int uid : uids) {
+                    uidsToBlame.put(uid, true);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Dumps the relevant stats for cpu wakeups and their attribution to subsystem and uids */
+    public synchronized void dump(IndentingPrintWriter pw, long nowElapsed) {
+        pw.println("CPU wakeup stats:");
+        pw.increaseIndent();
+
+        mIrqDeviceMap.dump(pw);
+        pw.println();
+
+        mRecentWakingActivity.dump(pw, nowElapsed);
+        pw.println();
+
+        final SparseLongArray attributionStats = new SparseLongArray();
+        pw.println("Wakeup events:");
+        pw.increaseIndent();
+        for (int i = mWakeupEvents.size() - 1; i >= 0; i--) {
+            TimeUtils.formatDuration(mWakeupEvents.keyAt(i), nowElapsed, pw);
+            pw.println(":");
+
+            pw.increaseIndent();
+            final Wakeup wakeup = mWakeupEvents.valueAt(i);
+            pw.println(wakeup);
+            pw.print("Attribution: ");
+            final SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+                    wakeup.mElapsedMillis);
+            if (attribution == null) {
+                pw.println("N/A");
+            } else {
+                for (int subsystemIdx = 0; subsystemIdx < attribution.size(); subsystemIdx++) {
+                    if (subsystemIdx > 0) {
+                        pw.print(", ");
+                    }
+                    final long counters = attributionStats.get(attribution.keyAt(subsystemIdx),
+                            IntPair.of(0, 0));
+                    int attributed = IntPair.first(counters);
+                    final int total = IntPair.second(counters) + 1;
+
+                    pw.print("subsystem: " + subsystemToString(attribution.keyAt(subsystemIdx)));
+                    pw.print(", uids: [");
+                    final SparseBooleanArray uids = attribution.valueAt(subsystemIdx);
+                    if (uids != null) {
+                        for (int uidIdx = 0; uidIdx < uids.size(); uidIdx++) {
+                            if (uidIdx > 0) {
+                                pw.print(", ");
+                            }
+                            UserHandle.formatUid(pw, uids.keyAt(uidIdx));
+                        }
+                        attributed++;
+                    }
+                    pw.print("]");
+
+                    attributionStats.put(attribution.keyAt(subsystemIdx),
+                            IntPair.of(attributed, total));
+                }
+                pw.println();
+            }
+            pw.decreaseIndent();
+        }
+        pw.decreaseIndent();
+
+        pw.println("Attribution stats:");
+        pw.increaseIndent();
+        for (int i = 0; i < attributionStats.size(); i++) {
+            pw.print("Subsystem " + subsystemToString(attributionStats.keyAt(i)));
+            pw.print(": ");
+            final long ratio = attributionStats.valueAt(i);
+            pw.println(IntPair.first(ratio) + "/" + IntPair.second(ratio));
+        }
+        pw.println("Total: " + mWakeupEvents.size());
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    private static final class WakingActivityHistory {
+        private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours.
+        private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
+                new SparseArray<>();
+
+        void recordActivity(int subsystem, long elapsedRealtime, int... uids) {
+            if (uids == null) {
+                return;
+            }
+            TimeSparseArray<SparseBooleanArray> wakingActivity = mWakingActivity.get(subsystem);
+            if (wakingActivity == null) {
+                wakingActivity = new TimeSparseArray<>();
+                mWakingActivity.put(subsystem, wakingActivity);
+            }
+            SparseBooleanArray uidsToBlame = wakingActivity.get(elapsedRealtime);
+            if (uidsToBlame == null) {
+                uidsToBlame = new SparseBooleanArray(uids.length);
+                wakingActivity.put(elapsedRealtime, uidsToBlame);
+            }
+            for (int i = 0; i < uids.length; i++) {
+                uidsToBlame.put(uids[i], true);
+            }
+            // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
+            // Note that the last activity is always present, even if it occurred before
+            // WAKING_ACTIVITY_RETENTION_MS.
+            final int endIdx = wakingActivity.closestIndexOnOrBefore(
+                    elapsedRealtime - WAKING_ACTIVITY_RETENTION_MS);
+            for (int i = endIdx; i >= 0; i--) {
+                wakingActivity.removeAt(endIdx);
+            }
+        }
+
+        void clearAllBefore(long elapsedRealtime) {
+            for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
+                final TimeSparseArray<SparseBooleanArray> activityPerSubsystem =
+                        mWakingActivity.valueAt(subsystemIdx);
+                final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
+                for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
+                    activityPerSubsystem.removeAt(removeIdx);
+                }
+                // Generally waking activity is a high frequency occurrence for any subsystem, so we
+                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
+                // This will leave one TimeSparseArray per subsystem, which are few right now.
+            }
+        }
+
+        SparseBooleanArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
+            final SparseBooleanArray uidsToReturn = new SparseBooleanArray();
+
+            final TimeSparseArray<SparseBooleanArray> activityForSubsystem =
+                    mWakingActivity.get(subsystem);
+            if (activityForSubsystem != null) {
+                final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
+                final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
+                for (int i = endIdx; i >= startIdx; i--) {
+                    final SparseBooleanArray uidsForTime = activityForSubsystem.valueAt(i);
+                    for (int j = 0; j < uidsForTime.size(); j++) {
+                        if (uidsForTime.valueAt(j)) {
+                            uidsToReturn.put(uidsForTime.keyAt(j), true);
+                        }
+                    }
+                }
+                // More efficient to remove in a separate loop as it avoids repeatedly calling gc().
+                for (int i = endIdx; i >= startIdx; i--) {
+                    activityForSubsystem.removeAt(i);
+                }
+                // Generally waking activity is a high frequency occurrence for any subsystem, so we
+                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
+                // This will leave one TimeSparseArray per subsystem, which are few right now.
+            }
+            return uidsToReturn.size() > 0 ? uidsToReturn : null;
+        }
+
+        void dump(IndentingPrintWriter pw, long nowElapsed) {
+            pw.println("Recent waking activity:");
+            pw.increaseIndent();
+            for (int i = 0; i < mWakingActivity.size(); i++) {
+                pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
+                final LongSparseArray<SparseBooleanArray> wakingActivity =
+                        mWakingActivity.valueAt(i);
+                if (wakingActivity == null) {
+                    continue;
+                }
+                pw.increaseIndent();
+                for (int j = wakingActivity.size() - 1; j >= 0; j--) {
+                    TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw);
+                    final SparseBooleanArray uidsToBlame = wakingActivity.valueAt(j);
+                    if (uidsToBlame == null) {
+                        pw.println();
+                        continue;
+                    }
+                    pw.print(": ");
+                    for (int k = 0; k < uidsToBlame.size(); k++) {
+                        if (uidsToBlame.valueAt(k)) {
+                            UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
+                            pw.print(", ");
+                        }
+                    }
+                    pw.println();
+                }
+                pw.decreaseIndent();
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    private SparseBooleanArray getResponsibleSubsystemsForWakeup(Wakeup wakeup) {
+        if (ArrayUtils.isEmpty(wakeup.mDevices)) {
+            return null;
+        }
+        final SparseBooleanArray result = new SparseBooleanArray();
+        for (final Wakeup.IrqDevice device : wakeup.mDevices) {
+            final List<String> rawSubsystems = mIrqDeviceMap.getSubsystemsForDevice(device.mDevice);
+
+            boolean anyKnownSubsystem = false;
+            if (rawSubsystems != null) {
+                for (int i = 0; i < rawSubsystems.size(); i++) {
+                    final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i));
+                    if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) {
+                        // Just in case the xml had arbitrary subsystem names, we want to make sure
+                        // that we only put the known ones into our attribution map.
+                        result.put(subsystem, true);
+                        anyKnownSubsystem = true;
+                    }
+                }
+            }
+            if (!anyKnownSubsystem) {
+                result.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true);
+            }
+        }
+        return result;
+    }
+
+    static int stringToKnownSubsystem(String rawSubsystem) {
+        switch (rawSubsystem) {
+            case SUBSYSTEM_ALARM_STRING:
+                return CPU_WAKEUP_SUBSYSTEM_ALARM;
+        }
+        return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+    }
+
+    static String subsystemToString(int subsystem) {
+        switch (subsystem) {
+            case CPU_WAKEUP_SUBSYSTEM_ALARM:
+                return SUBSYSTEM_ALARM_STRING;
+            case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
+                return "Unknown";
+        }
+        return "N/A";
+    }
+
+    private static final class Wakeup {
+        private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
+        private static final String ABORT_REASON_PREFIX = "Abort";
+        private static final Pattern sIrqPattern = Pattern.compile("(\\d+)\\s+(\\S+)");
+
+        String mRawReason;
+        long mElapsedMillis;
+        long mUptimeMillis;
+        IrqDevice[] mDevices;
+
+        Wakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
+            mRawReason = rawReason;
+            mElapsedMillis = elapsedMillis;
+            mUptimeMillis = uptimeMillis;
+            mDevices = parseIrqDevices(rawReason);
+        }
+
+        private static IrqDevice[] parseIrqDevices(String rawReason) {
+            final String[] components = rawReason.split(":");
+            if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) {
+                // We don't support parsing aborts yet.
+                return null;
+            }
+
+            int parsedDeviceCount = 0;
+            IrqDevice[] parsedDevices = new IrqDevice[components.length];
+
+            for (String component : components) {
+                final Matcher matcher = sIrqPattern.matcher(component);
+                if (matcher.find()) {
+                    final int line;
+                    final String device;
+                    try {
+                        line = Integer.parseInt(matcher.group(1));
+                        device = matcher.group(2);
+                    } catch (NumberFormatException e) {
+                        Slog.e(PARSER_TAG,
+                                "Exception while parsing device names from part: " + component, e);
+                        continue;
+                    }
+                    parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device);
+                }
+            }
+            return (parsedDeviceCount > 0) ? Arrays.copyOf(parsedDevices, parsedDeviceCount) : null;
+        }
+
+        @Override
+        public String toString() {
+            return "Wakeup{"
+                    + "mRawReason='" + mRawReason + '\''
+                    + ", mElapsedMillis=" + mElapsedMillis
+                    + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis)
+                    + ", mDevices=" + Arrays.toString(mDevices)
+                    + '}';
+        }
+
+        static final class IrqDevice {
+            int mLine;
+            String mDevice;
+
+            IrqDevice(int line, String device) {
+                mLine = line;
+                mDevice = device;
+            }
+
+            @Override
+            public String toString() {
+                return "IrqDevice{" + "mLine=" + mLine + ", mDevice='" + mDevice + '\'' + '}';
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
new file mode 100644
index 0000000..091d18e
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.power.stats;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LongSparseArray;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses irq_device_map.xml to store a mapping of devices that can send IRQs to the CPU to
+ * subsystems that represent some logical work happening on the device that could need an IRQ.
+ */
+public class IrqDeviceMap {
+    private static final String TAG_IRQ_DEVICE_MAP = "irq-device-map";
+    private static final String TAG_DEVICE = "device";
+    private static final String TAG_SUBSYSTEM = "subsystem";
+    private static final String ATTR_NAME = "name";
+
+    private static LongSparseArray<IrqDeviceMap> sInstanceMap = new LongSparseArray<>(1);
+
+    private final ArrayMap<String, List<String>> mSubsystemsForDevice = new ArrayMap();
+
+    private IrqDeviceMap(XmlResourceParser parser) {
+        try {
+            XmlUtils.beginDocument(parser, TAG_IRQ_DEVICE_MAP);
+
+            int type;
+            String currentDevice = null;
+            final ArraySet<String> subsystems = new ArraySet<>();
+
+            while ((type = parser.getEventType()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_DEVICE)) {
+                    currentDevice = parser.getAttributeValue(null, ATTR_NAME);
+                }
+                if (currentDevice != null && type == XmlPullParser.END_TAG
+                        && parser.getName().equals(TAG_DEVICE)) {
+                    final int n = subsystems.size();
+                    if (n > 0) {
+                        mSubsystemsForDevice.put(currentDevice,
+                                Collections.unmodifiableList(new ArrayList<>(subsystems)));
+                    }
+                    subsystems.clear();
+                    currentDevice = null;
+                }
+                if (currentDevice != null && type == XmlPullParser.START_TAG
+                        && parser.getName().equals(TAG_SUBSYSTEM)) {
+                    parser.next();
+                    if (parser.getEventType() == XmlPullParser.TEXT) {
+                        subsystems.add(parser.getText());
+                    }
+                }
+                parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            parser.close();
+        }
+    }
+
+    /**
+     * Returns an instance of IrqDeviceMap initialzed with the given context and xml resource.
+     * The xml resource should describe the mapping in a way similar to
+     * core/res/res/xml/irq_device_map.xml.
+     */
+    public static IrqDeviceMap getInstance(Context context, @XmlRes int resId) {
+        synchronized (IrqDeviceMap.class) {
+            final int idx = sInstanceMap.indexOfKey(resId);
+            if (idx >= 0) {
+                return sInstanceMap.valueAt(idx);
+            }
+        }
+        final XmlResourceParser parser = context.getResources().getXml(resId);
+        final IrqDeviceMap irqDeviceMap = new IrqDeviceMap(parser);
+        synchronized (IrqDeviceMap.class) {
+            sInstanceMap.put(resId, irqDeviceMap);
+        }
+        return irqDeviceMap;
+    }
+
+    List<String> getSubsystemsForDevice(String device) {
+        return mSubsystemsForDevice.get(device);
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Irq device map:");
+        pw.increaseIndent();
+
+        final LongSparseArray<IrqDeviceMap> instanceMap;
+        synchronized (IrqDeviceMap.class) {
+            instanceMap = sInstanceMap;
+        }
+        final int idx = instanceMap.indexOfValue(this);
+        final String res = (idx >= 0) ? ("0x" + Long.toHexString(instanceMap.keyAt(idx))) : null;
+        pw.println("Loaded from xml resource: " + res);
+
+        pw.println("Map:");
+        pw.increaseIndent();
+        for (int i = 0; i < mSubsystemsForDevice.size(); i++) {
+            pw.print(mSubsystemsForDevice.keyAt(i) + ": ");
+            pw.println(mSubsystemsForDevice.valueAt(i));
+        }
+        pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index fd6ec06..f744d00 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -46,6 +46,8 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
 
 class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
         SensorEventListener {
@@ -275,7 +277,7 @@
     public void onSensorChanged(SensorEvent event) {
         // Using log space to represent human sensation (Fechner's Law) instead of lux
         // because lux values causes bright flashes to skew the average very high.
-        addElement(event.timestamp, Math.max(0,
+        addElement(TimeUnit.NANOSECONDS.toMillis(event.timestamp), Math.max(0,
                 (int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER)));
         updateLightSession();
         mHandler.removeCallbacksAndMessages(mDelayedUpdateToken);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index c524bc4..f52f0b7 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1047,6 +1047,29 @@
         }
 
         @Override
+        public void notifyRecordingStarted(IBinder sessionToken, String recordingId, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyRecordingStarted");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingStarted(recordingId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingStarted", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+
+        }
+
+        @Override
         public void startInteractiveApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
@@ -2213,6 +2236,23 @@
         }
 
         @Override
+        public void onRequestStartRecording(Uri programUri) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestStartRecording: " + programUri);
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestStartRecording(programUri, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestStartRecording", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 321edc5..45b4ff8 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -25,20 +25,78 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.LinkedList;
+import java.util.Locale;
 
+/**
+ * Logs human-readable events for debugging purposes.
+ */
 public class EventLogger {
 
-    // ring buffer of events to log.
+    /** Identifies the source of events. */
+    private final String mTag;
+
+    /** Stores the events using a ring buffer. */
     private final LinkedList<Event> mEvents;
 
-    private final String mTitle;
-
-    // the maximum number of events to keep in log
+    /**
+     * The maximum number of events to keep in {@code mEvents}.
+     *
+     * <p>Calling {@link #log} when the size of {@link #mEvents} matches the threshold will
+     * cause the oldest event to be evicted.
+     */
     private final int mMemSize;
 
-    public static abstract class Event {
-        // formatter for timestamps
-        private final static SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
+    /**
+     * Constructor for logger.
+     * @param size the maximum number of events to keep in log
+     * @param tag the string displayed before the recorded log
+     */
+    public EventLogger(int size, String tag) {
+        mEvents = new LinkedList<Event>();
+        mMemSize = size;
+        mTag = tag;
+    }
+
+    public synchronized void log(Event evt) {
+        if (mEvents.size() >= mMemSize) {
+            mEvents.removeFirst();
+        }
+        mEvents.add(evt);
+    }
+
+    /**
+     * Add a string-based event to the log, and print it to logcat as info.
+     * @param msg the message for the logs
+     * @param tag the logcat tag to use
+     */
+    public synchronized void loglogi(String msg, String tag) {
+        final Event event = new StringEvent(msg);
+        log(event.printLog(tag));
+    }
+
+    /**
+     * Same as {@link #loglogi(String, String)} but specifying the logcat type
+     * @param msg the message for the logs
+     * @param logType the type of logcat entry
+     * @param tag the logcat tag to use
+     */
+    public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+        final Event event = new StringEvent(msg);
+        log(event.printLog(logType, tag));
+    }
+
+    public synchronized void dump(PrintWriter pw) {
+        pw.println("Events log: " + mTag);
+        for (Event evt : mEvents) {
+            pw.println(evt.toString());
+        }
+    }
+
+    public abstract static class Event {
+
+        /** Timestamps formatter. */
+        private static final SimpleDateFormat sFormat =
+                new SimpleDateFormat("MM-dd HH:mm:ss:SSS", Locale.US);
 
         private final long mTimestamp;
 
@@ -114,7 +172,7 @@
          * Timestamp information will be automatically added, do not include it.
          * @return a string representation of the event that occurred.
          */
-        abstract public String eventToString();
+        public abstract String eventToString();
     }
 
     public static class StringEvent extends Event {
@@ -129,50 +187,4 @@
             return mMsg;
         }
     }
-
-    /**
-     * Constructor for logger.
-     * @param size the maximum number of events to keep in log
-     * @param title the string displayed before the recorded log
-     */
-    public EventLogger(int size, String title) {
-        mEvents = new LinkedList<Event>();
-        mMemSize = size;
-        mTitle = title;
-    }
-
-    public synchronized void log(Event evt) {
-        if (mEvents.size() >= mMemSize) {
-            mEvents.removeFirst();
-        }
-        mEvents.add(evt);
-    }
-
-    /**
-     * Add a string-based event to the log, and print it to logcat as info.
-     * @param msg the message for the logs
-     * @param tag the logcat tag to use
-     */
-    public synchronized void loglogi(String msg, String tag) {
-        final Event event = new StringEvent(msg);
-        log(event.printLog(tag));
-    }
-
-    /**
-     * Same as {@link #loglogi(String, String)} but specifying the logcat type
-     * @param msg the message for the logs
-     * @param logType the type of logcat entry
-     * @param tag the logcat tag to use
-     */
-    public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
-        final Event event = new StringEvent(msg);
-        log(event.printLog(logType, tag));
-    }
-
-    public synchronized void dump(PrintWriter pw) {
-        pw.println("Audio event log: " + mTitle);
-        for (Event evt : mEvents) {
-            pw.println(evt.toString());
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ce7794d..78b657b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -256,6 +256,7 @@
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
 import android.app.admin.DevicePolicyManager;
+import android.app.assist.ActivityId;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -5585,7 +5586,9 @@
                         LocalServices.getService(ContentCaptureManagerInternal.class);
                 if (contentCaptureService != null) {
                     contentCaptureService.notifyActivityEvent(mUserId, mActivityComponent,
-                            ActivityEvent.TYPE_ACTIVITY_STARTED);
+                            ActivityEvent.TYPE_ACTIVITY_STARTED,
+                            new ActivityId(getTask() != null ? getTask().mTaskId : INVALID_TASK_ID,
+                                    shareableActivityToken));
                 }
                 break;
             case PAUSED:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7d23cca..416d546 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -152,6 +152,7 @@
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
 import android.app.admin.DevicePolicyCache;
+import android.app.assist.ActivityId;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.compat.CompatChanges;
@@ -4859,17 +4860,19 @@
 
     void updateActivityUsageStats(ActivityRecord activity, int event) {
         ComponentName taskRoot = null;
+        int taskId = INVALID_TASK_ID;
         final Task task = activity.getTask();
         if (task != null) {
             final ActivityRecord rootActivity = task.getRootActivity();
             if (rootActivity != null) {
                 taskRoot = rootActivity.mActivityComponent;
             }
+            taskId = task.mTaskId;
         }
-
         final Message m = PooledLambda.obtainMessage(
                 ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
-                activity.mActivityComponent, activity.mUserId, event, activity.token, taskRoot);
+                activity.mActivityComponent, activity.mUserId, event, activity.token, taskRoot,
+                new ActivityId(taskId, activity.shareableActivityToken));
         mH.sendMessage(m);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b473700..214a2c1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1481,7 +1481,7 @@
             handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
                     mRootWindowContainer.getDefaultTaskDisplayArea(), currentRootTask,
                     forceNonResizeable);
-            if (r != null) {
+            if (r != null && (options == null || !options.getDisableStartingWindow())) {
                 // Use a starting window to reduce the transition latency for reshowing the task.
                 // Note that with shell transition, this should be executed before requesting
                 // transition to avoid delaying the starting window.
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
new file mode 100644
index 0000000..5e44d6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 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.wm;
+
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
+
+/**
+ * The class that defines default launch params for tasks in desktop mode
+ */
+public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
+
+    private static final String TAG =
+            TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
+    private static final boolean DEBUG = false;
+
+    // Desktop mode feature flag.
+    static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode", false);
+    // Override default freeform task width when desktop mode is enabled. In dips.
+    private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt(
+            "persist.wm.debug.desktop_mode.default_width", 840);
+    // Override default freeform task height when desktop mode is enabled. In dips.
+    private static final int DESKTOP_MODE_DEFAULT_HEIGHT_DP = SystemProperties.getInt(
+            "persist.wm.debug.desktop_mode.default_height", 630);
+
+    private StringBuilder mLogBuilder;
+
+    @Override
+    public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+            @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+            @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+            LaunchParamsController.LaunchParams currentParams,
+            LaunchParamsController.LaunchParams outParams) {
+
+        initLogBuilder(task, activity);
+        int result = calculate(task, layout, activity, source, options, request, phase,
+                currentParams, outParams);
+        outputLog();
+        return result;
+    }
+
+    private int calculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+            @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+            @Nullable ActivityOptions options, @Nullable ActivityStarter.Request request, int phase,
+            LaunchParamsController.LaunchParams currentParams,
+            LaunchParamsController.LaunchParams outParams) {
+
+        if (task == null) {
+            appendLog("task null, skipping");
+            return RESULT_SKIP;
+        }
+        if (phase != PHASE_BOUNDS) {
+            appendLog("not in bounds phase, skipping");
+            return RESULT_SKIP;
+        }
+        if (!task.inFreeformWindowingMode()) {
+            appendLog("not a freeform task, skipping");
+            return RESULT_SKIP;
+        }
+        if (!currentParams.mBounds.isEmpty()) {
+            appendLog("currentParams has bounds set, not overriding");
+            return RESULT_SKIP;
+        }
+
+        // Copy over any values
+        outParams.set(currentParams);
+
+        // Update width and height with default desktop mode values
+        float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+        final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f);
+        final int height = (int) (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f);
+        outParams.mBounds.right = width;
+        outParams.mBounds.bottom = height;
+
+        // Center the task in window bounds
+        Rect windowBounds = task.getWindowConfiguration().getBounds();
+        outParams.mBounds.offset(windowBounds.centerX() - outParams.mBounds.centerX(),
+                windowBounds.centerY() - outParams.mBounds.centerY());
+
+        appendLog("setting desktop mode task bounds to %s", outParams.mBounds);
+
+        return RESULT_DONE;
+    }
+
+    private void initLogBuilder(Task task, ActivityRecord activity) {
+        if (DEBUG) {
+            mLogBuilder = new StringBuilder(
+                    "DesktopModeLaunchParamsModifier: task=" + task + " activity=" + activity);
+        }
+    }
+
+    private void appendLog(String format, Object... args) {
+        if (DEBUG) mLogBuilder.append(" ").append(String.format(format, args));
+    }
+
+    private void outputLog() {
+        if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b91d943..442777a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -28,8 +28,6 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -118,7 +116,6 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.InsetsFlags;
@@ -1150,24 +1147,6 @@
                 break;
             case TYPE_STATUS_BAR:
                 mStatusBar = win;
-                final TriConsumer<DisplayFrames, WindowContainer, Rect> gestureFrameProvider =
-                        (displayFrames, windowContainer, rect) -> {
-                            rect.bottom = rect.top + getStatusBarHeight(displayFrames);
-                            final DisplayCutout cutout =
-                                    displayFrames.mInsetsState.getDisplayCutout();
-                            if (cutout != null) {
-                                final Rect top = cutout.getBoundingRectTop();
-                                if (!top.isEmpty()) {
-                                    rect.bottom = Math.max(rect.bottom,
-                                            top.bottom + mDisplayCutoutTouchableRegionSize);
-                                }
-                            }
-                        };
-                mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, null);
-                mDisplayContent.setInsetProvider(
-                        ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
-                mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, null);
-                mInsetsSourceWindowsExceptIme.add(win);
                 break;
             case TYPE_NAVIGATION_BAR:
                 mNavigationBar = win;
@@ -1185,7 +1164,8 @@
                                                 displayFrames.mUnrestricted,
                                                 win.getBounds(), displayFrames.mDisplayCutoutSafe,
                                                 inOutFrame, provider.source,
-                                                provider.insetsSize, lp.privateFlags);
+                                                provider.insetsSize, lp.privateFlags,
+                                                provider.minimalInsetsSizeInDisplayCutoutSafe);
                                     }
                                 }
                                 inOutFrame.inset(win.mGivenContentInsets);
@@ -1231,85 +1211,91 @@
                 mInsetsSourceWindowsExceptIme.add(win);
                 if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
                 break;
-            default:
-                if (attrs.providedInsets != null) {
-                    for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
-                        final InsetsFrameProvider provider = attrs.providedInsets[i];
-                        switch (provider.type) {
-                            case ITYPE_STATUS_BAR:
-                                mStatusBarAlt = win;
-                                mStatusBarAltPosition = getAltBarPosition(attrs);
-                                break;
-                            case ITYPE_NAVIGATION_BAR:
-                                mNavigationBarAlt = win;
-                                mNavigationBarAltPosition = getAltBarPosition(attrs);
-                                break;
-                            case ITYPE_CLIMATE_BAR:
-                                mClimateBarAlt = win;
-                                mClimateBarAltPosition = getAltBarPosition(attrs);
-                                break;
-                            case ITYPE_EXTRA_NAVIGATION_BAR:
-                                mExtraNavBarAlt = win;
-                                mExtraNavBarAltPosition = getAltBarPosition(attrs);
-                                break;
+        }
+        // TODO(b/239145252): Temporarily skip the navigation bar as it is still with the hard-coded
+        // logic.
+        if (attrs.providedInsets != null && attrs.type != TYPE_NAVIGATION_BAR) {
+            for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
+                final InsetsFrameProvider provider = attrs.providedInsets[i];
+                switch (provider.type) {
+                    case ITYPE_STATUS_BAR:
+                        if (attrs.type != TYPE_STATUS_BAR) {
+                            mStatusBarAlt = win;
+                            mStatusBarAltPosition = getAltBarPosition(attrs);
                         }
-                        // The index of the provider and corresponding insets types cannot change at
-                        // runtime as ensured in WMS. Make use of the index in the provider directly
-                        // to access the latest provided size at runtime.
-                        final int index = i;
-                        final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
-                                provider.insetsSize != null
-                                        ? (displayFrames, windowContainer, inOutFrame) -> {
-                                            inOutFrame.inset(win.mGivenContentInsets);
+                        break;
+                    case ITYPE_NAVIGATION_BAR:
+                        if (attrs.type != TYPE_NAVIGATION_BAR) {
+                            mNavigationBarAlt = win;
+                            mNavigationBarAltPosition = getAltBarPosition(attrs);
+                        }
+                        break;
+                    case ITYPE_CLIMATE_BAR:
+                        mClimateBarAlt = win;
+                        mClimateBarAltPosition = getAltBarPosition(attrs);
+                        break;
+                    case ITYPE_EXTRA_NAVIGATION_BAR:
+                        mExtraNavBarAlt = win;
+                        mExtraNavBarAltPosition = getAltBarPosition(attrs);
+                        break;
+                }
+                // The index of the provider and corresponding insets types cannot change at
+                // runtime as ensured in WMS. Make use of the index in the provider directly
+                // to access the latest provided size at runtime.
+                final int index = i;
+                final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
+                        provider.insetsSize != null
+                                ? (displayFrames, windowContainer, inOutFrame) -> {
+                                    inOutFrame.inset(win.mGivenContentInsets);
+                                    final LayoutParams lp =
+                                            win.mAttrs.forRotation(displayFrames.mRotation);
+                                    final InsetsFrameProvider ifp =
+                                            win.mAttrs.forRotation(displayFrames.mRotation)
+                                                    .providedInsets[index];
+                                    InsetsFrameProvider.calculateInsetsFrame(
+                                            displayFrames.mUnrestricted,
+                                            windowContainer.getBounds(),
+                                            displayFrames.mDisplayCutoutSafe,
+                                            inOutFrame, ifp.source,
+                                            ifp.insetsSize, lp.privateFlags,
+                                            ifp.minimalInsetsSizeInDisplayCutoutSafe);
+                                } : null;
+                final InsetsFrameProvider.InsetsSizeOverride[] overrides =
+                        provider.insetsSizeOverrides;
+                final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
+                        overrideProviders;
+                if (overrides != null) {
+                    overrideProviders = new SparseArray<>();
+                    for (int j = overrides.length - 1; j >= 0; j--) {
+                        final int overrideIndex = j;
+                        final TriConsumer<DisplayFrames, WindowContainer, Rect>
+                                overrideFrameProvider =
+                                        (displayFrames, windowContainer, inOutFrame) -> {
                                             final LayoutParams lp =
-                                                    win.mAttrs.forRotation(displayFrames.mRotation);
+                                                    win.mAttrs.forRotation(
+                                                            displayFrames.mRotation);
                                             final InsetsFrameProvider ifp =
-                                                    win.mAttrs.forRotation(displayFrames.mRotation)
-                                                            .providedInsets[index];
+                                                    win.mAttrs.providedInsets[index];
                                             InsetsFrameProvider.calculateInsetsFrame(
                                                     displayFrames.mUnrestricted,
                                                     windowContainer.getBounds(),
                                                     displayFrames.mDisplayCutoutSafe,
                                                     inOutFrame, ifp.source,
-                                                    ifp.insetsSize, lp.privateFlags);
-                                        } : null;
-                        final InsetsFrameProvider.InsetsSizeOverride[] overrides =
-                                provider.insetsSizeOverrides;
-                        final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
-                                overrideProviders;
-                        if (overrides != null) {
-                            overrideProviders = new SparseArray<>();
-                            for (int j = overrides.length - 1; j >= 0; j--) {
-                                final int overrideIndex = j;
-                                final TriConsumer<DisplayFrames, WindowContainer, Rect>
-                                        overrideFrameProvider =
-                                                (displayFrames, windowContainer, inOutFrame) -> {
-                                                    final LayoutParams lp =
-                                                            win.mAttrs.forRotation(
-                                                                    displayFrames.mRotation);
-                                                    final InsetsFrameProvider ifp =
-                                                            win.mAttrs.providedInsets[index];
-                                                    InsetsFrameProvider.calculateInsetsFrame(
-                                                            displayFrames.mUnrestricted,
-                                                            windowContainer.getBounds(),
-                                                            displayFrames.mDisplayCutoutSafe,
-                                                            inOutFrame, ifp.source,
-                                                            ifp.insetsSizeOverrides[
-                                                                    overrideIndex].insetsSize,
-                                                            lp.privateFlags);
-                                                };
-                                overrideProviders.put(overrides[j].windowType,
-                                        overrideFrameProvider);
-                            }
-                        } else {
-                            overrideProviders = null;
-                        }
-                        mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
-                                overrideProviders);
-                        mInsetsSourceWindowsExceptIme.add(win);
+                                                    ifp.insetsSizeOverrides[
+                                                            overrideIndex].insetsSize,
+                                                    lp.privateFlags,
+                                                    null);
+                                        };
+                        overrideProviders.put(overrides[j].windowType,
+                                overrideFrameProvider);
                     }
+                } else {
+                    overrideProviders = null;
                 }
-                break;
+                mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
+                        overrideProviders);
+                mInsetsSourceWindowsExceptIme.add(win);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 7bd2a4a..e74e5787 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,6 +64,10 @@
     void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
         // {@link TaskLaunchParamsModifier} handles window layout preferences.
         registerModifier(new TaskLaunchParamsModifier(supervisor));
+        if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) {
+            // {@link DesktopModeLaunchParamsModifier} handles default task size changes
+            registerModifier(new DesktopModeLaunchParamsModifier());
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index a638784..92f7efd 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -265,7 +265,7 @@
             ActivityOptions options, int callingPid, int callingUid) {
         // If a launch task id is specified, then ensure that the caller is the recents
         // component or has the START_TASKS_FROM_RECENTS permission
-        if (options.getLaunchTaskId() != INVALID_TASK_ID
+        if ((options.getLaunchTaskId() != INVALID_TASK_ID || options.getDisableStartingWindow())
                 && !supervisor.mRecentTasks.isCallerRecents(callingUid)) {
             final int startInTaskPerm = ActivityTaskManagerService.checkPermission(
                     START_TASKS_FROM_RECENTS, callingPid, callingUid);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 155cf28..885968f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5058,6 +5058,9 @@
                     == ActivityOptions.ANIM_SCENE_TRANSITION) {
                 doShow = false;
             }
+            if (options != null && options.getDisableStartingWindow()) {
+                doShow = false;
+            }
             if (r.mLaunchTaskBehind) {
                 // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
                 // tell WindowManager that r is visible even though it is at the back of the root
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0f5184a..36389ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3868,8 +3868,8 @@
         // configuration update when the window has requested to be hidden. Doing so can lead to
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
-        if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
-                || mToken.isVisibleRequested()))) {
+        if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
+                || mActivityRecord.mVisibleRequested))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
             outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 98e5f1d..b914051 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -383,6 +383,7 @@
     </xs:complexType>
 
     <xs:complexType name="autoBrightness">
+        <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/>
         <xs:sequence>
             <!-- Sets the debounce for autoBrightness brightening in millis-->
             <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 748ef4b..d89bd7c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -6,9 +6,11 @@
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
     method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
+    method public boolean getEnabled();
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
     method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
+    method public void setEnabled(boolean);
   }
 
   public class BrightnessThresholds {
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..50996d7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+    private static final float FLOAT_DELTA = 0.001f;
+
+    private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+    @Before
+    public void before() {
+        mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+    }
+
+    @Test
+    public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+        float brightness = 0.3f;
+        float sdrBrightness = 0.2f;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+        DisplayBrightnessState displayBrightnessState =
+                mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
+                        sdrBrightness).setBrightnessReason(brightnessReason).build();
+
+        assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+        assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+    }
+
+    private String getString(DisplayBrightnessState displayBrightnessState) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DisplayBrightnessState:");
+        sb.append("\n    brightness:" + displayBrightnessState.getBrightness());
+        sb.append("\n    sdrBrightness:" + displayBrightnessState.getSdrBrightness());
+        sb.append("\n    brightnessReason:" + displayBrightnessState.getBrightnessReason());
+        return sb.toString();
+    }
+}
diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/servicestests/res/xml/irq_device_map_1.xml
new file mode 100644
index 0000000..1f1a77b
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+<irq-device-map>
+    <device name="test.device.1">
+        <subsystem>test.subsystem.1</subsystem>
+        <subsystem>test.subsystem.2</subsystem>
+    </device>
+    <device name="test.device.2">
+        <subsystem>test.subsystem.3</subsystem>
+        <subsystem>test.subsystem.2</subsystem>
+    </device>
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/servicestests/res/xml/irq_device_map_2.xml
new file mode 100644
index 0000000..508c98d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_2.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+
+<!-- Long comment describing anything that may be needed
+for this file and its maintenance -->
+<irq-device-map>
+    <!-- Small comment specific to this device -->
+    <invalid name="test.device.1">
+        <!-- These valid subsystem definitions should be ignored because of invalid parent tag -->
+        <subsystem>test.subsystem.1</subsystem>
+        <subsystem>test.subsystem.2</subsystem>
+    </invalid>
+    <device name="test.device.2">
+        <!-- Multiline comment to describe nuances
+         about why these subsystems
+         rely on this hardware device
+         to wake the CPU up from sleep
+        -->
+        <subsystem>test.subsystem.3</subsystem>
+        <!-- Small comment specific to test.subsystem.4 -->
+        <subsystem>test.subsystem.4</subsystem>
+        <subsystem>test.subsystem.5</subsystem>
+        <!-- Duplicates should be ignored -->
+        <subsystem>test.subsystem.4</subsystem>
+        <subsystem>test.subsystem.3</subsystem>
+        <subsystem>test.subsystem.5</subsystem>
+    </device>
+
+    <device name="test.device.3">
+        <!-- All child tags are invalid, mapping should be empty / non-existent for this device -->
+        <subsys>ignored</subsys>
+        <system>redundant</system>
+    </device>
+
+    <device name="test.device.4">
+        <!-- Invalid child tags should be skipped but others should be mapped -->
+        <invalid>unused</invalid>
+        <random>skipped</random>
+        <subsystem>test.subsystem.1</subsystem>
+        <subsystem>test.subsystem.4</subsystem>
+    </device>
+
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
new file mode 100644
index 0000000..498b676
--- /dev/null
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+<irq-device-map>
+    <device name="test.alarm.device">
+        <subsystem>Alarm</subsystem>
+    </device>
+    <device name="test.wifi.device">
+        <subsystem>undefined</subsystem>
+    </device>
+</irq-device-map>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 2d2c76c..0b776a3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -181,11 +181,8 @@
             doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
             mockIsUsersOnSecondaryDisplaysEnabled(false);
             // All UserController params are set to default.
-            mUserController = new UserController(mInjector);
 
-            // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most
-            // tests would fail. But we might need to disable it for the onBootComplete() test (i.e,
-            // to make sure the users are unlocked at the right time)
+            mUserController = new UserController(mInjector);
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 6388c7d..9c5d1a5 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -226,7 +226,7 @@
                 mContext.getSystemService(WindowManager.class), threadVerifier);
 
         mAssociationInfo = new AssociationInfo(1, 0, null,
-                MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
+                MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 5f3f3d7..65076a3 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -33,6 +33,7 @@
 import android.view.InputDevice
 import androidx.test.InstrumentationRegistry
 import com.android.server.input.BatteryController.UEventManager
+import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
 import org.hamcrest.Description
 import org.hamcrest.Matcher
 import org.hamcrest.MatcherAssert.assertThat
@@ -260,25 +261,28 @@
 
     @Test
     fun testListenersNotifiedOnUEventNotification() {
-        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
+        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1")
         `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
         val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+        // The device paths for UEvent notifications do not include the "/sys" prefix, so verify
+        // that the added listener is configured to match the path without that prefix.
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1"))
         listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
 
         // If the battery state has changed when an UEvent is sent, the listeners are notified.
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
-        uEventListener.value!!.onUEvent(TIMESTAMP)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
         listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f,
             eventTime = TIMESTAMP)
 
         // If the battery state has not changed when an UEvent is sent, the listeners are not
         // notified.
         clearInvocations(listener)
-        uEventListener.value!!.onUEvent(TIMESTAMP + 1)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
         verifyNoMoreInteractions(listener)
 
         batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
@@ -293,7 +297,7 @@
         `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
         `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
         val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
         batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
         verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
         listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 5e6cccc..1e67c12 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -75,15 +75,34 @@
     }
 
     @Test
-    public void testLskfBasedProtector() throws RemoteException {
+    public void testNoneLskfBasedProtector() throws RemoteException {
+        final int USER_ID = 10;
+        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
+                mGateKeeperService, mUserManager, mPasswordSlotManager);
+        SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
+        assertFalse(lskfGatekeeperHandleExists(USER_ID));
+        long protectorId = manager.createLskfBasedProtector(mGateKeeperService,
+                LockscreenCredential.createNone(), sp, USER_ID);
+        assertFalse(lskfGatekeeperHandleExists(USER_ID));
+
+        AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
+                protectorId, LockscreenCredential.createNone(), USER_ID, null);
+        assertArrayEquals(result.syntheticPassword.deriveKeyStorePassword(),
+                sp.deriveKeyStorePassword());
+    }
+
+    @Test
+    public void testNonNoneLskfBasedProtector() throws RemoteException {
         final int USER_ID = 10;
         final LockscreenCredential password = newPassword("user-password");
         final LockscreenCredential badPassword = newPassword("bad-password");
         MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                 mGateKeeperService, mUserManager, mPasswordSlotManager);
         SyntheticPassword sp = manager.newSyntheticPassword(USER_ID);
+        assertFalse(lskfGatekeeperHandleExists(USER_ID));
         long protectorId = manager.createLskfBasedProtector(mGateKeeperService, password, sp,
                 USER_ID);
+        assertTrue(lskfGatekeeperHandleExists(USER_ID));
 
         AuthenticationResult result = manager.unlockLskfBasedProtector(mGateKeeperService,
                 protectorId, password, USER_ID, null);
@@ -95,6 +114,10 @@
         assertNull(result.syntheticPassword);
     }
 
+    private boolean lskfGatekeeperHandleExists(int userId) throws RemoteException {
+        return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0;
+    }
+
     private boolean hasSyntheticPassword(int userId) throws RemoteException {
         return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
     }
@@ -430,6 +453,21 @@
     }
 
     @Test
+    public void testPasswordData_scryptParams() {
+        // CREDENTIAL_TYPE_NONE should result in the minimum scrypt params being used.
+        PasswordData data = PasswordData.create(CREDENTIAL_TYPE_NONE);
+        assertEquals(1, data.scryptLogN);
+        assertEquals(0, data.scryptLogR);
+        assertEquals(0, data.scryptLogP);
+
+        // Any other credential type should result in the real scrypt params being used.
+        data = PasswordData.create(CREDENTIAL_TYPE_PASSWORD);
+        assertTrue(data.scryptLogN > 1);
+        assertTrue(data.scryptLogR > 0);
+        assertTrue(data.scryptLogP > 0);
+    }
+
+    @Test
     public void testPasswordData_serializeDeserialize() {
         PasswordData data = new PasswordData();
         data.scryptLogN = 11;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 27c3ca4..a545b1f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -325,6 +325,7 @@
                 actual.getSessionErrorMessage());
         assertEquals(expected.isPrepared(), actual.isPrepared());
         assertEquals(expected.isCommitted(), actual.isCommitted());
+        assertEquals(expected.isPreapprovalRequested(), actual.isPreapprovalRequested());
         assertEquals(expected.createdMillis, actual.createdMillis);
         assertEquals(expected.isSealed(), actual.isSealed());
         assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
new file mode 100644
index 0000000..7731a32
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 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.power.stats;
+
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+
+import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
+import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+@RunWith(AndroidJUnit4.class)
+public class CpuWakeupStatsTest {
+    private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
+    private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
+    private static final String KERNEL_REASON_UNKNOWN = "unsupported-free-form-reason";
+
+    private static final int TEST_UID_1 = 13239823;
+    private static final int TEST_UID_2 = 25268423;
+    private static final int TEST_UID_3 = 92261423;
+    private static final int TEST_UID_4 = 56926423;
+    private static final int TEST_UID_5 = 76421423;
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
+
+    @Test
+    public void removesOldWakeups() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
+
+        final Set<Long> timestamps = new HashSet<>();
+        final long firstWakeup = 453192;
+
+        obj.noteWakeupTimeAndReason(firstWakeup, 32, "unused");
+        timestamps.add(firstWakeup);
+        for (int i = 1; i < 1000; i++) {
+            final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS);
+            if (timestamps.add(firstWakeup + delta)) {
+                obj.noteWakeupTimeAndReason(firstWakeup + delta, i, "unused");
+            }
+        }
+        assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
+
+        obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, "unused");
+        assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
+
+        for (int i = 0; i < 100; i++) {
+            final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS);
+            obj.noteWakeupTimeAndReason(now, i, "unused");
+            assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS))
+                    .isLessThan(0);
+        }
+    }
+
+    @Test
+    public void alarmIrqAttributionSolo() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final long wakeupTime = 12423121;
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
+
+        // Outside the window, so should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+        // Should be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
+
+        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(1);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+    }
+
+    @Test
+    public void alarmIrqAttributionCombined() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final long wakeupTime = 92123210;
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 4,
+                KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
+
+        // Outside the window, so should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+                wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
+                wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+        // Should be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
+                TEST_UID_5);
+
+        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(2);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true);
+        assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+    }
+
+    @Test
+    public void unknownIrqAttribution() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final long wakeupTime = 92123410;
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
+
+        // Unrelated subsystems, should not be attributed
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
+                TEST_UID_5);
+
+        final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+        assertThat(attribution).isNotNull();
+        assertThat(attribution.size()).isEqualTo(1);
+        assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+        final SparseBooleanArray uids = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
+        assertThat(uids == null || uids.size() == 0).isTrue();
+    }
+
+    @Test
+    public void unknownAttribution() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final long wakeupTime = 72123210;
+
+        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
+
+        // Unrelated subsystems, should be ignored.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
+
+        // There should be nothing in the attribution map.
+        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
new file mode 100644
index 0000000..43d9e60
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+import com.android.internal.util.CollectionUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class IrqDeviceMapTest {
+    private static final String TEST_DEVICE_1 = "test.device.1";
+    private static final String TEST_DEVICE_2 = "test.device.2";
+    private static final String TEST_DEVICE_3 = "test.device.3";
+    private static final String TEST_DEVICE_4 = "test.device.4";
+
+    private static final String TEST_SUBSYSTEM_1 = "test.subsystem.1";
+    private static final String TEST_SUBSYSTEM_2 = "test.subsystem.2";
+    private static final String TEST_SUBSYSTEM_3 = "test.subsystem.3";
+    private static final String TEST_SUBSYSTEM_4 = "test.subsystem.4";
+    private static final String TEST_SUBSYSTEM_5 = "test.subsystem.5";
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+    @Test
+    public void cachesInstancesPerXml() {
+        IrqDeviceMap irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+        IrqDeviceMap irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+        assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2);
+
+        irqDeviceMap2 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+        assertThat(irqDeviceMap1).isNotSameInstanceAs(irqDeviceMap2);
+
+        irqDeviceMap1 = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+        assertThat(irqDeviceMap1).isSameInstanceAs(irqDeviceMap2);
+    }
+
+    @Test
+    public void simpleXml() {
+        IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_1);
+
+        List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1);
+        assertThat(subsystems).hasSize(2);
+        // No specific order is required.
+        assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_1);
+
+        subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2);
+        assertThat(subsystems).hasSize(2);
+        // No specific order is required.
+        assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_2, TEST_SUBSYSTEM_3);
+    }
+
+    @Test
+    public void complexXml() {
+        IrqDeviceMap deviceMap = IrqDeviceMap.getInstance(sContext, R.xml.irq_device_map_2);
+
+        List<String> subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_1);
+        assertThat(CollectionUtils.isEmpty(subsystems)).isTrue();
+
+        subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_2);
+        assertThat(subsystems).hasSize(3);
+        // No specific order is required.
+        assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_3, TEST_SUBSYSTEM_4,
+                TEST_SUBSYSTEM_5);
+
+        subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_3);
+        assertThat(CollectionUtils.isEmpty(subsystems)).isTrue();
+
+        subsystems = deviceMap.getSubsystemsForDevice(TEST_DEVICE_4);
+        assertThat(subsystems).hasSize(2);
+        // No specific order is required.
+        assertThat(subsystems).containsExactly(TEST_SUBSYSTEM_4, TEST_SUBSYSTEM_1);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
new file mode 100644
index 0000000..7830e90
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 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.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for desktop mode task bounds.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DesktopModeLaunchParamsModifierTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
+
+    private ActivityRecord mActivity;
+
+    private DesktopModeLaunchParamsModifier mTarget;
+
+    private LaunchParamsController.LaunchParams mCurrent;
+    private LaunchParamsController.LaunchParams mResult;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = new ActivityBuilder(mAtm).build();
+        mTarget = new DesktopModeLaunchParamsModifier();
+        mCurrent = new LaunchParamsController.LaunchParams();
+        mCurrent.reset();
+        mResult = new LaunchParamsController.LaunchParams();
+        mResult.reset();
+    }
+
+    @Test
+    public void testReturnsSkipIfTaskIsNull() {
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
+    }
+
+    @Test
+    public void testReturnsSkipIfNotBoundsPhase() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase(
+                PHASE_DISPLAY).calculate());
+    }
+
+    @Test
+    public void testReturnsSkipIfTaskNotInFreeform() {
+        final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).build();
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
+    public void testReturnsSkipIfCurrentParamsHasBounds() {
+        final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+                WINDOWING_MODE_FREEFORM).build();
+        mCurrent.mBounds.set(/* left */ 0, /* top */ 0, /* right */ 100, /* bottom */ 100);
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
+    public void testUsesDefaultBounds() {
+        final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+                WINDOWING_MODE_FREEFORM).build();
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(dpiToPx(task, 840), mResult.mBounds.width());
+        assertEquals(dpiToPx(task, 630), mResult.mBounds.height());
+    }
+
+    @Test
+    public void testUsesDisplayAreaAndWindowingModeFromSource() {
+        final Task task = new TaskBuilder(mSupervisor).setWindowingMode(
+                WINDOWING_MODE_FREEFORM).build();
+        TaskDisplayArea mockTaskDisplayArea = mock(TaskDisplayArea.class);
+        mCurrent.mPreferredTaskDisplayArea = mockTaskDisplayArea;
+        mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(mockTaskDisplayArea, mResult.mPreferredTaskDisplayArea);
+        assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+    }
+
+    private int dpiToPx(Task task, int dpi) {
+        float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
+        return (int) (dpi * density + 0.5f);
+    }
+
+    private class CalculateRequestBuilder {
+        private Task mTask;
+        private int mPhase = PHASE_BOUNDS;
+        private final ActivityRecord mActivity =
+                DesktopModeLaunchParamsModifierTests.this.mActivity;
+        private final LaunchParamsController.LaunchParams mCurrentParams = mCurrent;
+        private final LaunchParamsController.LaunchParams mOutParams = mResult;
+
+        private CalculateRequestBuilder setTask(Task task) {
+            mTask = task;
+            return this;
+        }
+
+        private CalculateRequestBuilder setPhase(int phase) {
+            mPhase = phase;
+            return this;
+        }
+
+        @Result
+        private int calculate() {
+            return mTarget.onCalculate(mTask, /* layout*/ null, mActivity, /* source */
+                    null, /* options */ null, /* request */ null, mPhase, mCurrentParams,
+                    mOutParams);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 041f298..f2bc47d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -21,6 +21,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -45,6 +47,7 @@
 
 import android.app.StatusBarManager;
 import android.platform.test.annotations.Presubmit;
+import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -70,7 +73,7 @@
 
     @Test
     public void testControlsForDispatch_regular() {
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -82,7 +85,7 @@
 
     @Test
     public void testControlsForDispatch_multiWindowTaskVisible() {
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
@@ -95,7 +98,7 @@
 
     @Test
     public void testControlsForDispatch_freeformTaskVisible() {
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
@@ -108,8 +111,7 @@
 
     @Test
     public void testControlsForDispatch_forceStatusBarVisible() {
-        addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
-                PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+        addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -123,7 +125,7 @@
     public void testControlsForDispatch_statusBarForceShowNavigation() {
         addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
                 PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -152,7 +154,7 @@
     public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
         mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
         mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -163,7 +165,7 @@
 
     @Test
     public void testControlsForDispatch_topAppHidesStatusBar() {
-        addWindow(TYPE_STATUS_BAR, "statusBar");
+        addStatusBar();
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         // Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
@@ -256,7 +258,7 @@
     @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
-        final WindowState statusBar = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar");
+        final WindowState statusBar = addStatusBar();
         statusBar.setHasSurface(true);
         statusBar.getControllableInsetProvider().setServerVisible(true);
         final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
@@ -298,8 +300,7 @@
     @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
-        addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
-                .getControllableInsetProvider().getSource().setVisible(false);
+        addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
         addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
                 .getControllableInsetProvider().setServerVisible(true);
 
@@ -328,8 +329,8 @@
     @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
-        final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
-                .getControllableInsetProvider().getSource();
+        final InsetsSource statusBarSource =
+                addStatusBar().getControllableInsetProvider().getSource();
         final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
                 .getControllableInsetProvider().getSource();
         statusBarSource.setVisible(false);
@@ -381,8 +382,7 @@
 
     @Test
     public void testShowTransientBars_abortsWhenControlTargetChanges() {
-        addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
-                .getControllableInsetProvider().getSource().setVisible(false);
+        addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
         addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
                 .getControllableInsetProvider().getSource().setVisible(false);
         final WindowState app = addWindow(TYPE_APPLICATION, "app");
@@ -406,6 +406,18 @@
         return win;
     }
 
+    private WindowState addStatusBar() {
+        final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+        win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+        win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(ITYPE_STATUS_BAR),
+                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+        };
+        mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+        return win;
+    }
+
     private WindowState addWindow(int type, String name) {
         final WindowState win = createWindow(null, type, name);
         mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 54b33e97..d59fce0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -26,6 +26,8 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -84,6 +86,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
+import android.view.InsetsFrameProvider;
 import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
@@ -3079,6 +3082,11 @@
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
+        attrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(ITYPE_STATUS_BAR),
+                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+        };
         final TestWindowState statusBar = new TestWindowState(
                 displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
         token.addWindow(statusBar);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8370691..cf24ff2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -217,6 +217,12 @@
         mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
                 outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
         assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
+        // Non activity window can still get the last config.
+        win.mActivityRecord = null;
+        win.fillClientWindowFramesAndConfiguration(outFrames, outConfig,
+                false /* useLatestConfig */, true /* relayoutVisible */);
+        assertEquals(win.getConfiguration().densityDpi,
+                outConfig.getMergedConfiguration().densityDpi);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index c597b46..40326e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -27,6 +27,9 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Process.SYSTEM_UID;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -324,6 +327,11 @@
             mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             mStatusBarWindow.mAttrs.setFitInsetsTypes(0);
+            mStatusBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                    new InsetsFrameProvider(ITYPE_STATUS_BAR),
+                    new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+                    new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+            };
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
             mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 79546b8..151ff80 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1242,6 +1242,19 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
         @Override
         public void updateState(
+                @Nullable PersistableBundle options,
+                @Nullable SharedMemory sharedMemory) {
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService();
+
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.updateStateLocked(options, sharedMemory));
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
+        @Override
+        public void initAndVerifyDetector(
                 @NonNull Identity voiceInteractorIdentity,
                 @Nullable PersistableBundle options,
                 @Nullable SharedMemory sharedMemory,
@@ -1250,21 +1263,12 @@
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
-                if (mImpl == null) {
-                    Slog.w(TAG, "updateState without running voice interaction service");
-                    return;
-                }
-
                 voiceInteractorIdentity.uid = Binder.getCallingUid();
                 voiceInteractorIdentity.pid = Binder.getCallingPid();
 
-                final long caller = Binder.clearCallingIdentity();
-                try {
-                    mImpl.updateStateLocked(
-                            voiceInteractorIdentity, options, sharedMemory, callback, detectorType);
-                } finally {
-                    Binder.restoreCallingIdentity(caller);
-                }
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.initAndVerifyDetectorLocked(voiceInteractorIdentity, options,
+                                sharedMemory, callback, detectorType));
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index dcf7b78..5d1901d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -551,12 +551,31 @@
     }
 
     public void updateStateLocked(
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory) {
+        Slog.v(TAG, "updateStateLocked");
+
+        if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
+            Slog.w(TAG, "Can't set sharedMemory to be read-only");
+            throw new IllegalStateException("Can't set sharedMemory to be read-only");
+        }
+
+        if (mHotwordDetectionConnection == null) {
+            Slog.w(TAG, "update State, but no hotword detection connection");
+            throw new IllegalStateException("Hotword detection connection not found");
+        }
+        synchronized (mHotwordDetectionConnection.mLock) {
+            mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
+        }
+    }
+
+    public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
             int detectorType) {
-        Slog.v(TAG, "updateStateLocked");
+        Slog.v(TAG, "initAndVerifyDetectorLocked");
         int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (mHotwordDetectionComponentName == null) {
             Slog.w(TAG, "Hotword detection service name not found");
@@ -614,8 +633,6 @@
                     mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
                     mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
                     options, sharedMemory, callback, detectorType);
-        } else {
-            mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index a27b2da..f810fbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -47,6 +47,7 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
+            tapl.workspace.switchToOverview().dismissAllTasks()
             testApp.launchViaIntent(wmHelper)
             testApp.openIME(wmHelper)
             this.setRotation(testSpec.startRotation)
@@ -61,7 +62,7 @@
     }
 
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         // depends on how much of the animation transactions are sent to SF at once
@@ -79,23 +80,23 @@
     }
 
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
                 ignoreWindows =
-                    listOf(
-                        ComponentNameMatcher.SPLASH_SCREEN,
-                        ComponentNameMatcher.SNAPSHOT,
-                        component
-                    )
+                listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    component
+                )
             )
         }
     }
 
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     fun launcherWindowBecomesInvisible() {
         testSpec.assertWm {
@@ -105,11 +106,11 @@
         }
     }
 
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
 
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     fun imeAppWindowIsAlwaysVisible() {
         // the app starts visible in live tile, and stays visible for the duration of entering
@@ -119,13 +120,13 @@
         testSpec.assertWm { this.isAppWindowVisible(testApp) }
     }
 
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     fun imeLayerBecomesVisible() {
         testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
     }
 
-    @FlakyTest(bugId = 251214932)
+    @Presubmit
     @Test
     fun appLayerReplacesLauncher() {
         testSpec.assertLayers {
@@ -137,60 +138,6 @@
         }
     }
 
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() {
-        super.navBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun navBarWindowIsAlwaysVisible() {
-        super.navBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun entireScreenCovered() {
-        super.entireScreenCovered()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-        super.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() {
-        super.statusBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        super.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 251214932)
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() {
-        super.taskBarWindowIsAlwaysVisible()
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 7ccfeb7..73e6d22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -81,87 +81,101 @@
         }
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun appWindowReplacesLauncherAsTopWindow() =
         super.appWindowReplacesLauncherAsTopWindow()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+    @FlakyTest(bugId = 240916028) @Test override fun focusChanges() = super.focusChanges()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() =
         super.statusBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 240916028)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
     /** {@inheritDoc} */
-    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
 
     companion object {
         /**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 46186bc..bc1f0d1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -104,7 +103,7 @@
      * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @FlakyTest(bugId = 250520840)
+    @Presubmit
     @Test
     open fun startsWithApp2LayersCoverFullScreen() {
         testSpec.assertLayersStart {
@@ -236,15 +235,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    @FlakyTest(bugId = 250518877)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 7a1350e..f988bb2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -23,6 +23,7 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -61,8 +62,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
-     * start and end of the WM trace
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
+     * the start and end of the WM trace
      */
     @Presubmit
     @Test
@@ -71,8 +72,18 @@
         testSpec.navBarWindowIsVisibleAtStartAndEnd()
     }
 
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 250520840)
+    @Test
+    override fun startsWithApp2LayersCoverFullScreen() =
+        super.startsWithApp2LayersCoverFullScreen()
+
     @FlakyTest(bugId = 246284708)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 0a21044..7e4504b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
@@ -116,7 +115,7 @@
      * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @FlakyTest(bugId = 250522691)
+    @Presubmit
     @Test
     open fun startsWithApp1LayersCoverFullScreen() {
         testSpec.assertLayersStart {
@@ -255,10 +254,6 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
-    @FlakyTest(bugId = 250518877)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 03647c9..cc954ab 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -23,6 +23,7 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -62,8 +63,8 @@
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
     /**
-     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
-     * start and end of the WM trace
+     * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
+     * the start and end of the WM trace
      */
     @Presubmit
     @Test
@@ -76,4 +77,13 @@
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    @FlakyTest(bugId = 250522691)
+    @Test
+    override fun startsWithApp1LayersCoverFullScreen() =
+        super.startsWithApp1LayersCoverFullScreen()
 }
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 06a96df..8aaf18a 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -87,6 +87,7 @@
                 .setHasSensor(true)
                 .setHasBattery(true)
                 .setCountryCode(InputDeviceCountryCode.INTERNATIONAL)
+                .setSupportsUsi(true)
                 .build();
 
         Parcel parcel = Parcel.obtain();
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 0fb062f..0b61948 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -39,7 +39,7 @@
         kotlin.system.exitProcess(2)
     }
 
-    val ancestorCollector = AncestorCollector(Opcodes.ASM7, null)
+    val ancestorCollector = AncestorCollector(Opcodes.ASM9, null)
 
     for (entry in zipFile.entries()) {
         if (entry.name.endsWith(".class")) {
diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
index 863f976..67c5561 100644
--- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
+++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java
@@ -28,7 +28,7 @@
     private final TraceInjectionConfiguration mParams;
     public TraceInjectionClassVisitor(ClassVisitor classVisitor,
             TraceInjectionConfiguration params) {
-        super(Opcodes.ASM7, classVisitor);
+        super(Opcodes.ASM9, classVisitor);
         mParams = params;
     }
 
diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
index c2bbddc..91e987d 100644
--- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
+++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java
@@ -61,7 +61,7 @@
 
     public TraceInjectionMethodAdapter(MethodVisitor methodVisitor, int access,
             String name, String descriptor, TraceInjectionConfiguration params) {
-        super(Opcodes.ASM7, methodVisitor, access, name, descriptor);
+        super(Opcodes.ASM9, methodVisitor, access, name, descriptor);
         mParams = params;
         mIsConstructor = "<init>".equals(name);
     }
@@ -157,7 +157,7 @@
     class TracingAnnotationVisitor extends AnnotationVisitor {
 
         TracingAnnotationVisitor(AnnotationVisitor annotationVisitor) {
-            super(Opcodes.ASM7, annotationVisitor);
+            super(Opcodes.ASM9, annotationVisitor);
         }
 
         @Override