Merge "Inflate public notification view" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index efd8578..c231b30 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -57,7 +57,7 @@
     ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.credentials.flags-aconfig-java{.generated_srcjars}",
     ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}",
+    ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
     ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
     ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
@@ -588,16 +588,16 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
-// Pinner Service
+// Server Services Flags
 aconfig_declarations {
-    name: "com.android.server.flags.pinner-aconfig",
+    name: "com.android.server.flags.services-aconfig",
     package: "com.android.server.flags",
-    srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"],
+    srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
 }
 
 java_aconfig_library {
-    name: "com.android.server.flags.pinner-aconfig-java",
-    aconfig_declarations: "com.android.server.flags.pinner-aconfig",
+    name: "com.android.server.flags.services-aconfig-java",
+    aconfig_declarations: "com.android.server.flags.services-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
diff --git a/Android.bp b/Android.bp
index bb93048..13b1703 100644
--- a/Android.bp
+++ b/Android.bp
@@ -174,6 +174,9 @@
         // and remove this line.
         "//frameworks/base/tools/hoststubgen:__subpackages__",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // AIDL files under these paths are mixture of public and private ones.
@@ -264,6 +267,9 @@
     ],
     sdk_version: "core_platform",
     installable: false,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -432,6 +438,9 @@
     ],
     sdk_version: "core_platform",
     installable: false,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -475,6 +484,9 @@
     ],
     compile_dex: false,
     headers_only: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -502,6 +514,9 @@
             "-Xep:AndroidFrameworkUid:ERROR",
         ],
     },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -516,6 +531,7 @@
     },
     lint: {
         enabled: false,
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -540,6 +556,9 @@
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -555,6 +574,9 @@
         "calendar-provider-compat-config",
         "contacts-provider-platform-compat-config",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 platform_compat_config {
@@ -609,6 +631,9 @@
         "rappor",
     ],
     dxflags: ["--core-library"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // utility classes statically linked into framework-wifi and dynamically linked
@@ -634,6 +659,9 @@
         "//frameworks/base/services/net",
         "//packages/modules/Wifi/framework",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 filegroup {
diff --git a/SECURITY_STATE_OWNERS b/SECURITY_STATE_OWNERS
new file mode 100644
index 0000000..30ddfe2
--- /dev/null
+++ b/SECURITY_STATE_OWNERS
@@ -0,0 +1,5 @@
+alxu@google.com
+musashi@google.com
+maunik@google.com
+davidkwak@google.com
+willcoster@google.com
\ No newline at end of file
diff --git a/apct-tests/perftests/healthconnect/Android.bp b/apct-tests/perftests/healthconnect/Android.bp
new file mode 100644
index 0000000..c2d0a6f
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "HealthConnectPerfTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "apct-perftests-utils",
+        "collector-device-lib-platform",
+    ],
+
+    libs: ["android.test.base"],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
+    certificate: "platform",
+}
diff --git a/apct-tests/perftests/healthconnect/AndroidManifest.xml b/apct-tests/perftests/healthconnect/AndroidManifest.xml
new file mode 100644
index 0000000..6a6370b
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.perftests.healthconnect">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.perftests.healthconnect"/>
+</manifest>
diff --git a/apct-tests/perftests/healthconnect/AndroidTest.xml b/apct-tests/perftests/healthconnect/AndroidTest.xml
new file mode 100644
index 0000000..5036202
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidTest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs HealthConnectPerfTests metric instrumentation.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="HealthConnectPerfTests.apk" />
+    </target_preparer>
+
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.perftests.healthconnect" />
+        <option name="hidden-api-checks" value="false"/>
+
+         <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+    </test>
+</configuration>
diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
new file mode 100644
index 0000000..da0b46a
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1219472
+
+arkivanov@google.com
+jstembridge@google.com
+pratyushmore@google.com
+itsleo@google.com
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
new file mode 100644
index 0000000..21a4ca0
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.perftests.healthconnect
+
+import android.health.connect.HealthConnectManager
+import android.os.SystemClock
+import android.perftests.utils.PerfStatusReporter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Read/write benchmark tests for [HealthConnectManager]
+ *
+ * Build/Install/Run: atest HealthConnectReadWritePerfTest
+ */
+@RunWith(AndroidJUnit4::class)
+class HealthConnectReadWritePerfTest {
+
+    @get:Rule
+    val perfStatusReporter = PerfStatusReporter()
+
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+
+    private val manager by lazy {
+        requireNotNull(context.getSystemService(HealthConnectManager::class.java))
+    }
+
+    /**
+     * A first empty test just to setup the test package and make sure it runs properly.
+     */
+    @Test
+    fun placeholder() {
+        val state = perfStatusReporter.benchmarkState
+        while (state.keepRunning()) {
+            SystemClock.sleep(100)
+        }
+    }
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 900c902..d940e38 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1830,7 +1830,12 @@
                     /* system_measured_calling_download_bytes */0,
                     /* system_measured_calling_upload_bytes */ 0,
                     jobStatus.getJob().getIntervalMillis(),
-                    jobStatus.getJob().getFlexMillis());
+                    jobStatus.getJob().getFlexMillis(),
+                    jobStatus.hasFlexibilityConstraint(),
+                    /* isFlexConstraintSatisfied */ false,
+                    jobStatus.canApplyTransportAffinities(),
+                    jobStatus.getNumAppliedFlexibleConstraints(),
+                    jobStatus.getNumDroppedFlexibleConstraints());
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -2273,7 +2278,12 @@
                     /* system_measured_calling_download_bytes */0,
                     /* system_measured_calling_upload_bytes */ 0,
                     cancelled.getJob().getIntervalMillis(),
-                    cancelled.getJob().getFlexMillis());
+                    cancelled.getJob().getFlexMillis(),
+                    cancelled.hasFlexibilityConstraint(),
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+                    cancelled.canApplyTransportAffinities(),
+                    cancelled.getNumAppliedFlexibleConstraints(),
+                    cancelled.getNumDroppedFlexibleConstraints());
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3addf9f..fe55e27 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -536,7 +536,12 @@
                     /* system_measured_calling_download_bytes */ 0,
                     /* system_measured_calling_upload_bytes */ 0,
                     job.getJob().getIntervalMillis(),
-                    job.getJob().getFlexMillis());
+                    job.getJob().getFlexMillis(),
+                    job.hasFlexibilityConstraint(),
+                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+                    job.canApplyTransportAffinities(),
+                    job.getNumAppliedFlexibleConstraints(),
+                    job.getNumDroppedFlexibleConstraints());
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1620,7 +1625,12 @@
                 TrafficStats.getUidTxBytes(completedJob.getUid())
                         - mInitialUploadedBytesFromCalling,
                 completedJob.getJob().getIntervalMillis(),
-                completedJob.getJob().getFlexMillis());
+                completedJob.getJob().getFlexMillis(),
+                completedJob.hasFlexibilityConstraint(),
+                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+                completedJob.canApplyTransportAffinities(),
+                completedJob.getNumAppliedFlexibleConstraints(),
+                completedJob.getNumDroppedFlexibleConstraints());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0d5d11e..bdc2246 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -106,11 +106,8 @@
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static final int CONSTRAINT_BATTERY_NOT_LOW =
             JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -125,7 +122,7 @@
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
     static final int CONSTRAINT_PREFETCH = 1 << 23;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
-    static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+    public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
 
     private static final int IMPLICIT_CONSTRAINTS = 0
             | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
@@ -1534,7 +1531,7 @@
 
     /**
      * Returns the number of required flexible job constraints that have been dropped with time.
-     * The lower this number is the easier it is for the flexibility constraint to be satisfied.
+     * The higher this number is the easier it is for the flexibility constraint to be satisfied.
      */
     public int getNumDroppedFlexibleConstraints() {
         return mNumDroppedFlexibleConstraints;
@@ -1600,7 +1597,8 @@
         mTransportAffinitiesSatisfied = isSatisfied;
     }
 
-    boolean canApplyTransportAffinities() {
+    /** Whether transport affinities can be applied to the job in flex scheduling. */
+    public boolean canApplyTransportAffinities() {
         return mCanApplyTransportAffinities;
     }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index ace00fc..df073dd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9315,10 +9315,14 @@
   public final class StorageStats implements android.os.Parcelable {
     method public int describeContents();
     method public long getAppBytes();
+    method @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public long getAppBytesByDataType(int);
     method public long getCacheBytes();
     method public long getDataBytes();
     method public long getExternalCacheBytes();
     method public void writeToParcel(android.os.Parcel, int);
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
   }
 
@@ -12928,6 +12932,7 @@
     field public static final String FEATURE_MIDI = "android.software.midi";
     field public static final String FEATURE_NFC = "android.hardware.nfc";
     field public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+    field @FlaggedApi("android.nfc.enable_nfc_charging") public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
     field public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
     field public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
     field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
@@ -13418,12 +13423,12 @@
 
   public final class SigningInfo implements android.os.Parcelable {
     ctor public SigningInfo();
-    ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
+    ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
     ctor public SigningInfo(android.content.pm.SigningInfo);
     method public int describeContents();
     method public android.content.pm.Signature[] getApkContentsSigners();
     method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
-    method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
+    method @FlaggedApi("android.content.pm.archiving") public int getSchemeVersion();
     method public android.content.pm.Signature[] getSigningCertificateHistory();
     method public boolean hasMultipleSigners();
     method public boolean hasPastSigningCertificates();
@@ -23972,6 +23977,7 @@
     method @Nullable public android.net.Uri getIconUri();
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
     method public int getType();
     method public int getVolume();
     method public int getVolumeHandling();
@@ -23989,6 +23995,9 @@
     field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
     field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1
     field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
@@ -24029,6 +24038,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -24242,6 +24252,7 @@
     method public void release();
     method public void selectRoute(@NonNull android.media.MediaRoute2Info);
     method public void setVolume(int);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
   }
 
   public abstract static class MediaRouter2.TransferCallback {
@@ -24639,12 +24650,16 @@
     method @Nullable public CharSequence getName();
     method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
     method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason();
     method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1
   }
 
   public static final class RoutingSessionInfo.Builder {
@@ -24665,6 +24680,8 @@
     method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
     method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int);
@@ -28797,6 +28814,7 @@
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
@@ -28804,6 +28822,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28889,6 +28908,20 @@
     ctor public TagLostException(String);
   }
 
+  @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+    ctor public WlcLDeviceInfo(double, double, double, int);
+    method public int describeContents();
+    method public double getBatteryLevel();
+    method public double getProductId();
+    method public int getState();
+    method public double getTemperature();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONNECTED_CHARGING = 2; // 0x2
+    field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+    field public static final int DISCONNECTED = 1; // 0x1
+  }
+
 }
 
 package android.nfc.cardemulation {
@@ -45666,6 +45699,7 @@
     field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED";
     field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
     field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+    field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
     field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
     field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
     field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
@@ -57395,7 +57429,7 @@
     method public abstract boolean getBuiltInZoomControls();
     method public abstract int getCacheMode();
     method public abstract String getCursiveFontFamily();
-    method public abstract boolean getDatabaseEnabled();
+    method @Deprecated public abstract boolean getDatabaseEnabled();
     method @Deprecated public abstract String getDatabasePath();
     method public abstract int getDefaultFixedFontSize();
     method public abstract int getDefaultFontSize();
@@ -57441,7 +57475,7 @@
     method public abstract void setBuiltInZoomControls(boolean);
     method public abstract void setCacheMode(int);
     method public abstract void setCursiveFontFamily(String);
-    method public abstract void setDatabaseEnabled(boolean);
+    method @Deprecated public abstract void setDatabaseEnabled(boolean);
     method @Deprecated public abstract void setDatabasePath(String);
     method public abstract void setDefaultFixedFontSize(int);
     method public abstract void setDefaultFontSize(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 51e61e6..0d4169f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3357,7 +3357,7 @@
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
     method public int describeContents();
-    method @StringRes public int getDisplayNameStringRes();
+    method @NonNull public String getName();
     method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
@@ -3367,7 +3367,7 @@
     ctor public VirtualCameraConfig.Builder();
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
-    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
   }
 
@@ -9884,17 +9884,20 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
     field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
@@ -9909,6 +9912,10 @@
     method public boolean onUnlockAttempted(android.nfc.Tag);
   }
 
+  @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+    method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+  }
+
 }
 
 package android.nfc.cardemulation {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index aaeba66..812ba6d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -510,6 +510,7 @@
     method public int getActivityType();
     method @Nullable public android.graphics.Rect getAppBounds();
     method @NonNull public android.graphics.Rect getBounds();
+    method public int getDisplayRotation();
     method @NonNull public android.graphics.Rect getMaxBounds();
     method public int getRotation();
     method public int getWindowingMode();
@@ -3611,6 +3612,10 @@
     method @Nullable public android.view.View getStatusBarBackgroundView();
   }
 
+  public static final class WindowInsets.Type {
+    method @NonNull public static String toString(int);
+  }
+
   public interface WindowManager extends android.view.ViewManager {
     method public default int getDisplayImePolicy(int);
     method public static boolean hasWindowExtensionsEnabled();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6ddb36a..6df0f6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -407,7 +407,7 @@
 
     private int mLastSessionId;
     // Holds the value of the last reported device ID value from the server for the top activity.
-    int mLastReportedDeviceId;
+    int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
     final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
     @UnsupportedAppUsage
     final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -4856,10 +4856,13 @@
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
             if (!service.isUiContext()) { // WindowProviderService is a UI Context.
-                VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
-                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
-                        || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
                     service.updateDeviceId(mLastReportedDeviceId);
+                } else {
+                    VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+                    if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+                        service.updateDeviceId(mLastReportedDeviceId);
+                    }
                 }
             }
             service.onCreate();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b7db5f5..c3adbc3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -222,7 +222,7 @@
     boolean removeAutomaticZenRule(String id, boolean fromUser);
     boolean removeAutomaticZenRules(String packageName, boolean fromUser);
     int getRuleInstanceCount(in ComponentName owner);
-    void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
+    void setAutomaticZenRuleState(String id, in Condition condition);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f76a45b..0b6e24c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1391,20 +1391,9 @@
      * @param condition The new state of this rule
      */
     public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
-        if (Flags.modesApi()) {
-            setAutomaticZenRuleState(id, condition,
-                    /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
-        } else {
-            setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
-        }
-    }
-
-    /** @hide */
-    public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
-            boolean fromUser) {
         INotificationManager service = getService();
         try {
-            service.setAutomaticZenRuleState(id, condition, fromUser);
+            service.setAutomaticZenRuleState(id, condition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 4621634..aa3b71a 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -27,6 +27,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Configuration;
@@ -326,7 +327,7 @@
     }
 
     /**
-     * Sets the apparent display cutout.
+     * Sets the display rotation.
      * @hide
      */
     public void setDisplayRotation(@Surface.Rotation int rotation) {
@@ -386,9 +387,9 @@
     }
 
     /**
-     * @see #setDisplayRotation
-     * @hide
+     * Gets the display rotation.
      */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
     public @Surface.Rotation int getDisplayRotation() {
         return mDisplayRotation;
     }
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 7418c06..9f97f6f 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,7 +16,7 @@
 
 package android.app.servertransaction;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import static java.util.Objects.requireNonNull;
 
@@ -67,7 +67,7 @@
      * window configuration.
      */
     public void onDisplayChanged(int displayId) {
-        if (!isSyncWindowConfigUpdateFlagEnabled()) {
+        if (!isBundleClientTransactionFlagEnabled()) {
             return;
         }
         if (ActivityThread.isSystem()) {
@@ -77,9 +77,9 @@
         mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
     }
 
-    /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */
-    public boolean isSyncWindowConfigUpdateFlagEnabled() {
+    /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
+    public boolean isBundleClientTransactionFlagEnabled() {
         // Can't read flag from isolated process.
-        return !Process.isIsolated() && syncWindowConfigUpdateFlag();
+        return !Process.isIsolated() && bundleClientTransactionFlag();
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 9f5e0dc..ee48e43 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -32,7 +32,7 @@
 import static android.app.servertransaction.TransactionExecutorHelper.tId;
 import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import android.annotation.NonNull;
 import android.app.ActivityThread.ActivityClientRecord;
@@ -183,9 +183,9 @@
         }
 
         // Can't read flag from isolated process.
-        final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
-                && syncWindowConfigUpdateFlag();
-        final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+        final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
+                && bundleClientTransactionFlag();
+        final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
                 ? item.getContextToUpdate(mTransactionHandler)
                 : null;
         final Configuration preExecutedConfig = configUpdatedContext != null
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 8d25d7b..87d97d5 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -17,11 +17,16 @@
 package android.app.usage;
 
 import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Storage statistics for a UID, package, or {@link UserHandle} on a single
  * storage volume.
@@ -29,10 +34,47 @@
  * @see StorageStatsManager
  */
 public final class StorageStats implements Parcelable {
-    /** {@hide} */ public long codeBytes;
-    /** {@hide} */ public long dataBytes;
-    /** {@hide} */ public long cacheBytes;
-    /** {@hide} */ public long externalCacheBytes;
+    /** @hide */ public long codeBytes;
+    /** @hide */ public long dataBytes;
+    /** @hide */ public long cacheBytes;
+    /** @hide */ public long apkBytes;
+    /** @hide */ public long libBytes;
+    /** @hide */ public long dmBytes;
+    /** @hide */ public long externalCacheBytes;
+
+    /** Represents all .apk files in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+
+    /** Represents all .dm files in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+
+    /** Represents lib/ in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of lib/ directory.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_LIB = 2;
+
+    /**
+     * Keep in sync with the file types defined above.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    @IntDef(flag = false, value = {
+        APP_DATA_TYPE_FILE_TYPE_APK,
+        APP_DATA_TYPE_FILE_TYPE_DM,
+        APP_DATA_TYPE_LIB,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AppDataType {}
 
     /**
      * Return the size of app. This includes {@code APK} files, optimized
@@ -48,6 +90,27 @@
     }
 
     /**
+     * Return the size of the specified data type. This includes files stored under
+     * application code path.
+     * <p>
+     * If there is more than one package inside a uid, the return represents the aggregated
+     * stats when query StorageStat for package or uid.
+     * The data  is not collected and the return defaults to 0 when query StorageStats for user.
+     *
+     * <p>
+     * Data is isolated for each user on a multiuser device.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public long getAppBytesByDataType(@AppDataType int dataType) {
+        switch (dataType) {
+          case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
+          case APP_DATA_TYPE_LIB: return libBytes;
+          case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
+          default: return 0;
+        }
+    }
+
+    /**
      * Return the size of all data. This includes files stored under
      * {@link Context#getDataDir()}, {@link Context#getCacheDir()},
      * {@link Context#getCodeCacheDir()}.
@@ -98,6 +161,9 @@
         this.codeBytes = in.readLong();
         this.dataBytes = in.readLong();
         this.cacheBytes = in.readLong();
+        this.apkBytes = in.readLong();
+        this.libBytes = in.readLong();
+        this.dmBytes = in.readLong();
         this.externalCacheBytes = in.readLong();
     }
 
@@ -111,6 +177,9 @@
         dest.writeLong(codeBytes);
         dest.writeLong(dataBytes);
         dest.writeLong(cacheBytes);
+        dest.writeLong(apkBytes);
+        dest.writeLong(libBytes);
+        dest.writeLong(dmBytes);
         dest.writeLong(externalCacheBytes);
     }
 
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index a611255..4d9d911 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -35,3 +35,10 @@
     description: " Feature flag to support filter based event query API"
     bug: "194321117"
 }
+
+flag {
+    name: "get_app_bytes_by_data_type_api"
+    namespace: "system_performance"
+    description: "Feature flag for collecting app data size by file type API"
+    bug: "294088945"
+}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index c95b864..ec2e5fe 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -12,4 +12,11 @@
   namespace: "app_widgets"
   description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
   bug: "245950570"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "remove_app_widget_service_io_from_critical_path"
+  namespace: "app_widgets"
+  description: "Move state file IO to non-critical path"
+  bug: "312949280"
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index a939251..59fe9a1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -20,11 +20,9 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.companion.virtual.flags.Flags;
-import android.content.res.Resources;
 import android.graphics.ImageFormat;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -45,16 +43,16 @@
 @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
 public final class VirtualCameraConfig implements Parcelable {
 
-    private final @StringRes int mNameStringRes;
+    private final String mName;
     private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
     private final IVirtualCameraCallback mCallback;
 
     private VirtualCameraConfig(
-            int displayNameStringRes,
+            @NonNull String name,
             @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
             @NonNull Executor executor,
             @NonNull VirtualCameraCallback callback) {
-        mNameStringRes = displayNameStringRes;
+        mName = requireNonNull(name, "Missing name");
         mStreamConfigurations =
                 Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
         if (mStreamConfigurations.isEmpty()) {
@@ -68,7 +66,7 @@
     }
 
     private VirtualCameraConfig(@NonNull Parcel in) {
-        mNameStringRes = in.readInt();
+        mName = in.readString8();
         mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
         mStreamConfigurations =
                 Set.of(
@@ -84,18 +82,18 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mNameStringRes);
+        dest.writeString8(mName);
         dest.writeStrongInterface(mCallback);
         dest.writeParcelableArray(
                 mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
     }
 
     /**
-     * @return The display name of this VirtualCamera
+     * @return The name of this VirtualCamera
      */
-    @StringRes
-    public int getDisplayNameStringRes() {
-        return mNameStringRes;
+    @NonNull
+    public String getName() {
+        return mName;
     }
 
     /**
@@ -126,30 +124,22 @@
      * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
      * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
      *     VirtualCameraCallback)}
-     * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
+     * <li>A camera name must be set with {@link #setName(String)}
      */
     @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
     public static final class Builder {
 
-        private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+        private String mName;
         private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
         private Executor mCallbackExecutor;
         private VirtualCameraCallback mCallback;
 
         /**
-         * Set the visible name of this camera for the user.
-         *
-         * <p>Sets the resource to a string representing a user readable name for this virtual
-         * camera.
-         *
-         * @throws IllegalArgumentException if an invalid resource id is passed.
+         * Set the name of the virtual camera instance.
          */
         @NonNull
-        public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
-            if (displayNameStringRes <= 0) {
-                throw new IllegalArgumentException("Invalid resource passed for display name");
-            }
-            mDisplayNameStringRes = displayNameStringRes;
+        public Builder setName(@NonNull String name) {
+            mName = requireNonNull(name, "Display name cannot be null");
             return this;
         }
 
@@ -203,7 +193,7 @@
         @NonNull
         public VirtualCameraConfig build() {
             return new VirtualCameraConfig(
-                    mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
+                    mName, mStreamConfigurations, mCallbackExecutor, mCallback);
         }
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a863870..8151a91 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
@@ -55,7 +57,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -91,6 +92,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageInfoCommonUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DataClass;
 
@@ -817,6 +822,8 @@
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
             MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+            MATCH_DIRECT_BOOT_AWARE,
+            MATCH_DIRECT_BOOT_UNAWARE,
             GET_ATTRIBUTIONS_LONG,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -2518,6 +2525,7 @@
             USER_MIN_ASPECT_RATIO_16_9,
             USER_MIN_ASPECT_RATIO_3_2,
             USER_MIN_ASPECT_RATIO_FULLSCREEN,
+            USER_MIN_ASPECT_RATIO_APP_DEFAULT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserMinAspectRatio {}
@@ -2571,6 +2579,16 @@
      */
     public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
 
+    /**
+     * Aspect ratio override code: user sets to app's default aspect ratio.
+     * This resets both the user-forced aspect ratio, and the device manufacturer
+     * per-app override {@link ActivityInfo#OVERRIDE_ANY_ORIENTATION_TO_USER}.
+     * It is different from {@link #USER_MIN_ASPECT_RATIO_UNSET} as the latter may
+     * apply the device manufacturer per-app orientation override if any,
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_APP_DEFAULT = 7;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
@@ -3295,6 +3313,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports NFC charging.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_NFC_CHARGING)
+    public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The Beam API is enabled on the device.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -3304,7 +3330,7 @@
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports any
      * one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
-     * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
+     * {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF}, or {@link #FEATURE_NFC_CHARGING} features.
      *
      * @hide
      */
@@ -8609,28 +8635,56 @@
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
             @NonNull PackageInfoFlags flags) {
-        long flagsBits = flags.getValue();
-        final PackageParser parser = new PackageParser();
-        parser.setCallback(new PackageParser.CallbackImpl(this));
         final File apkFile = new File(archiveFilePath);
-        try {
-            if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
-                // Caller expressed an explicit opinion about what encryption
-                // aware/unaware components they want to see, so fall through and
-                // give them what they want
-            } else {
-                // Caller expressed no opinion, so match everything
-                flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
-            }
 
-            PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
-            if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
-                PackageParser.collectCertificates(pkg, false /* skipVerify */);
-            }
-            return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
-                    FrameworkPackageUserState.DEFAULT);
-        } catch (PackageParser.PackageParserException e) {
-            Log.w(TAG, "Failure to parse package archive", e);
+        @PackageInfoFlagsBits long flagsBits = flags.getValue();
+        if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
+            // Caller expressed an explicit opinion about what encryption
+            // aware/unaware components they want to see, so fall through and
+            // give them what they want
+        } else {
+            // Caller expressed no opinion, so match everything
+            flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+        }
+
+        int parserFlags = 0;
+        if ((flagsBits & (GET_SIGNATURES | GET_SIGNING_CERTIFICATES)) != 0) {
+            parserFlags |= PARSE_COLLECT_CERTIFICATES;
+        }
+
+        final PackageParser2 parser2 = new PackageParser2(/*separateProcesses*/ null,
+                /*displayMetrics*/ null,/*cacher*/ null,
+                new PackageParser2.Callback() {
+                    @Override
+                    public boolean hasFeature(String feature) {
+                        return PackageManager.this.hasSystemFeature(feature);
+                    }
+
+                    @NonNull
+                    @Override
+                    public Set<String> getHiddenApiWhitelistedApps() {
+                        return Collections.emptySet();
+                    }
+
+                    @NonNull
+                    @Override
+                    public Set<String> getInstallConstraintsAllowlist() {
+                        return Collections.emptySet();
+                    }
+
+                    @Override
+                    public boolean isChangeEnabled(long changeId,
+                            @androidx.annotation.NonNull ApplicationInfo appInfo) {
+                        return false;
+                    }
+                });
+
+        try {
+            ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);
+
+            return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
+        } catch (PackageParserException e) {
+            Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
             return null;
         }
     }
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 81cfc07..b919c4b 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -55,6 +55,18 @@
     /** Size of cache used by the application. (e.g., /data/data/<app>/cache) */
     public long cacheSize;
 
+    /** Size of .apk files of the application. */
+    /** @hide */
+    public long apkSize;
+
+    /** Size of the libraries of the application. */
+    /** @hide */
+    public long libSize;
+
+    /** Size of the .dm files of the application. */
+    /** @hide */
+    public long dmSize;
+
     /**
      * Size of the secure container on external storage holding the
      * application's code.
@@ -108,6 +120,18 @@
             sb.append(" cache=");
             sb.append(cacheSize);
         }
+        if (apkSize != 0) {
+            sb.append(" apk=");
+            sb.append(apkSize);
+        }
+        if (libSize != 0) {
+            sb.append(" lib=");
+            sb.append(libSize);
+        }
+        if (dmSize != 0) {
+            sb.append(" dm=");
+            sb.append(dmSize);
+        }
         if (externalCodeSize != 0) {
             sb.append(" extCode=");
             sb.append(externalCodeSize);
@@ -149,6 +173,9 @@
         codeSize = source.readLong();
         dataSize = source.readLong();
         cacheSize = source.readLong();
+        apkSize = source.readLong();
+        libSize = source.readLong();
+        dmSize = source.readLong();
         externalCodeSize = source.readLong();
         externalDataSize = source.readLong();
         externalCacheSize = source.readLong();
@@ -162,6 +189,9 @@
         codeSize = pStats.codeSize;
         dataSize = pStats.dataSize;
         cacheSize = pStats.cacheSize;
+        apkSize = pStats.apkSize;
+        libSize = pStats.libSize;
+        dmSize = pStats.dmSize;
         externalCodeSize = pStats.externalCodeSize;
         externalDataSize = pStats.externalDataSize;
         externalCacheSize = pStats.externalCacheSize;
@@ -179,6 +209,9 @@
         dest.writeLong(codeSize);
         dest.writeLong(dataSize);
         dest.writeLong(cacheSize);
+        dest.writeLong(apkSize);
+        dest.writeLong(libSize);
+        dest.writeLong(dmSize);
         dest.writeLong(externalCodeSize);
         dest.writeLong(externalDataSize);
         dest.writeLong(externalCacheSize);
@@ -198,6 +231,9 @@
                 && codeSize == otherStats.codeSize
                 && dataSize == otherStats.dataSize
                 && cacheSize == otherStats.cacheSize
+                && apkSize == otherStats.apkSize
+                && libSize == otherStats.libSize
+                && dmSize == otherStats.dmSize
                 && externalCodeSize == otherStats.externalCodeSize
                 && externalDataSize == otherStats.externalDataSize
                 && externalCacheSize == otherStats.externalCacheSize
@@ -208,7 +244,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(packageName, userHandle, codeSize, dataSize,
-                cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize,
+                apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+                externalDataSize, externalCacheSize, externalMediaSize,
                 externalObbSize);
     }
 
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index 8c21974..bb09ad2 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -31,6 +31,8 @@
 
 import libcore.util.HexEncoding;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.PublicKey;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
@@ -49,6 +51,7 @@
 
     private static final String TAG = "SigningDetails";
 
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef({SignatureSchemeVersion.UNKNOWN,
             SignatureSchemeVersion.JAR,
             SignatureSchemeVersion.SIGNING_BLOCK_V2,
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index a407704..23daaf2 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -17,9 +17,9 @@
 package android.content.pm;
 
 import android.annotation.FlaggedApi;
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -53,7 +53,7 @@
      * schemas</a>
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public SigningInfo(@IntRange(from = 0) int schemeVersion,
+    public SigningInfo(@SignatureSchemeVersion int schemeVersion,
             @Nullable Collection<Signature> apkContentsSigners,
             @Nullable Collection<PublicKey> publicKeys,
             @Nullable Collection<Signature> signingCertificateHistory) {
@@ -168,7 +168,7 @@
      * schemas</a>
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public @IntRange(from = 0) int getSchemeVersion() {
+    public @SignatureSchemeVersion int getSchemeVersion() {
         return mSigningDetails.getSignatureSchemeVersion();
     }
 
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 60d5c14..94bec35 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -123,3 +123,11 @@
     bug: "306329516"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "improve_home_app_behavior"
+    namespace: "package_manager_service"
+    description: "Feature flag to improve the uninstallation and preferred activity of home app."
+    bug: "310801107"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796f..c7797c7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@
     bug: "296829976"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "allow_resolver_sheet_for_private_space"
+    namespace: "profile_experiences"
+    description: "Add support for Private Space in resolver sheet"
+    bug: "307515485"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03db..60ad8e8 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@
     private static native long nativeCreate(String opPackageName);
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
+    private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+            Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@
         // initialize the sensor list
         for (int index = 0;; ++index) {
             Sensor sensor = new Sensor();
-            if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+                if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+            } else {
+                if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            }
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
-
     }
 
     /** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889d..665d8d2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@
      * on a particular SessionConfiguration.</p>
      *
      * @return List of CameraCharacteristic keys containing characterisitics specific to a session
-     * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+     * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+     * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
      */
     @NonNull
     @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
     public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
         if (mAvailableSessionCharacteristicsKeys == null) {
-            mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+            mAvailableSessionCharacteristicsKeys =
+                    Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
         }
         return mAvailableSessionCharacteristicsKeys;
     }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..64a62a9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@
          */
         boolean blockScreenOn(Runnable unblocker);
 
+        /**
+         * Get the brightness levels used to determine automatic brightness based on lux levels.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The brightness levels for the specified mode. The values are between
+         * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+         */
+        float[] getAutoBrightnessLevels(int mode);
+
+        /**
+         * Get the lux levels used to determine automatic brightness.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The lux levels for the specified mode
+         */
+        float[] getAutoBrightnessLuxLevels(int mode);
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f6beec1..967a0cc 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -30,8 +30,10 @@
 import android.nfc.INfcUnlockHandler;
 import android.nfc.ITagRemovedCallback;
 import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
 import android.nfc.NfcAntennaInfo;
 import android.os.Bundle;
+import android.nfc.WlcLDeviceInfo;
 
 /**
  * @hide
@@ -86,4 +88,11 @@
     boolean enableReaderOption(boolean enable);
     boolean isObserveModeSupported();
     boolean setObserveMode(boolean enabled);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean enableWlc(boolean enable);
+    boolean isWlcEnabled();
+    void registerWlcStateListener(in INfcWlcStateListener listener);
+    void unregisterWlcStateListener(in INfcWlcStateListener listener);
+    WlcLDeviceInfo getWlcLDeviceInfo();
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java b/core/java/android/nfc/INfcWlcStateListener.aidl
similarity index 62%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
rename to core/java/android/nfc/INfcWlcStateListener.aidl
index a979cf8..c2b7075 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
+++ b/core/java/android/nfc/INfcWlcStateListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.model.installstagedata;
+package android.nfc;
 
-public class InstallStaging extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_STAGING;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
+import android.nfc.WlcLDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+  /**
+   * Called whenever NFC WLC state changes
+   *
+   * @param wlcLDeviceInfo NFC wlc listener information
+   */
+  void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f407fb7..21e23ae 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -75,6 +75,7 @@
     static final String TAG = "NFC";
 
     private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+    private final NfcWlcStateListener mNfcWlcStateListener;
 
     /**
      * Intent to start an activity when a tag with NDEF payload is discovered.
@@ -440,6 +441,7 @@
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
     static boolean sHasCeFeature;
+    static boolean sHasNfcWlcFeature;
 
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -650,8 +652,9 @@
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+            sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
             /* is this device meant to have NFC */
-            if (!sHasNfcFeature && !sHasCeFeature) {
+            if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
                 Log.v(TAG, "this device does not have NFC support");
                 throw new UnsupportedOperationException();
             }
@@ -776,6 +779,7 @@
         mTagRemovedListener = null;
         mLock = new Object();
         mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+        mNfcWlcStateListener = new NfcWlcStateListener(getService());
     }
 
     /**
@@ -944,7 +948,8 @@
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
         }
-        return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
+        return serviceState
+                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
     }
 
     /**
@@ -2587,4 +2592,159 @@
             return false;
         }
     }
+
+    /**
+     * Sets NFC charging feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enableWlc(boolean enable) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.enableWlc(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.enableWlc(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC charging feature is enabled.
+     *
+     * @return True if NFC charging is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public boolean isWlcEnabled() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isWlcEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isWlcEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerWlcStateListener} and disable it with {@link
+     * NfcAdapter#unregisterWlcStateListenerListener}.
+     * @see #registerWlcStateListener
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public interface WlcStateListener {
+        /**
+         * Called on NFC WLC state changes
+         */
+        void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+    }
+
+    /**
+     * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+     * <p>The provided listener will be invoked by the given {@link Executor}.
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void registerWlcStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.register(executor, listener);
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     * <p>The same {@link WlcStateListener} object used when calling
+     * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+     * must be used.
+     *
+     * <p>Listeners are automatically unregistered when application process goes away
+     *
+     * @param listener user implementation of the {@link WlcStateListener}a
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void unregisterWlcStateListener(
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.unregister(listener);
+    }
+
+    /**
+     * Returns information on the NFC charging listener device
+     *
+     * @return Information on the NFC charging listener device
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @Nullable
+    public WlcLDeviceInfo getWlcLDeviceInfo() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.getWlcLDeviceInfo();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getWlcLDeviceInfo();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/core/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 0000000..8d79310
--- /dev/null
+++ b/core/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+    private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+    private final INfcAdapter mAdapter;
+
+    private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+    private WlcLDeviceInfo mCurrentState = null;
+    private boolean mIsRegistered = false;
+
+    public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Register a {@link WlcStateListener} with this
+     * {@link WlcStateListener}
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.put(listener, executor);
+
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerWlcStateListener(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register");
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     *
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void unregister(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (!mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.remove(listener);
+
+            if (mListenerMap.isEmpty() && mIsRegistered) {
+                try {
+                    mAdapter.unregisterWlcStateListener(this);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister");
+                }
+                mIsRegistered = false;
+            }
+        }
+    }
+
+    private void sendCurrentState(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            Executor executor = mListenerMap.get(listener);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> listener.onWlcStateChanged(
+                        mCurrentState));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+        synchronized (this) {
+            mCurrentState = wlcLDeviceInfo;
+
+            for (WlcStateListener cb : mListenerMap.keySet()) {
+                sendCurrentState(cb);
+            }
+        }
+    }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/core/java/android/nfc/WlcLDeviceInfo.aidl
similarity index 71%
copy from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
copy to core/java/android/nfc/WlcLDeviceInfo.aidl
index b8a9355..33143fe 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/core/java/android/nfc/WlcLDeviceInfo.aidl
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.ui;
+package android.nfc;
 
-public interface UninstallActionListener {
-
-    void onPositiveResponse(boolean keepData);
-
-    void onNegativeResponse();
-}
+parcelable WlcLDeviceInfo;
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/core/java/android/nfc/WlcLDeviceInfo.java
new file mode 100644
index 0000000..016431e
--- /dev/null
+++ b/core/java/android/nfc/WlcLDeviceInfo.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcLDeviceInfo implements Parcelable {
+    public static final int DISCONNECTED = 1;
+
+    public static final int CONNECTED_CHARGING = 2;
+
+    public static final int CONNECTED_DISCHARGING = 3;
+
+    private double mProductId;
+    private double mTemperature;
+    private double mBatteryLevel;
+    private int mState;
+
+    public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
+        this.mProductId = productId;
+        this.mTemperature = temperature;
+        this.mBatteryLevel = batteryLevel;
+        this.mState = state;
+    }
+
+    /**
+     * ProductId of the WLC listener device.
+     */
+    public double getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * Temperature of the WLC listener device.
+     */
+    public double getTemperature() {
+        return mTemperature;
+    }
+
+    /**
+     * BatteryLevel of the WLC listener device.
+     */
+    public double getBatteryLevel() {
+        return mBatteryLevel;
+    }
+
+    /**
+     * State of the WLC listener device.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    private WlcLDeviceInfo(Parcel in) {
+        this.mProductId = in.readDouble();
+        this.mTemperature = in.readDouble();
+        this.mBatteryLevel = in.readDouble();
+        this.mState = in.readInt();
+    }
+
+    public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
+            new Parcelable.Creator<WlcLDeviceInfo>() {
+                @Override
+                public WlcLDeviceInfo createFromParcel(Parcel in) {
+                    return new WlcLDeviceInfo(in);
+                }
+
+                @Override
+                public WlcLDeviceInfo[] newArray(int size) {
+                    return new WlcLDeviceInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeDouble(mProductId);
+        dest.writeDouble(mTemperature);
+        dest.writeDouble(mBatteryLevel);
+        dest.writeDouble(mState);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f9..41dee3a 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
 package android.nfc.cardemulation;
 
 import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@
         // Set uid
         mUid = si.applicationInfo.uid;
 
-        mCategoryOtherServiceEnabled = false;    // support other category
+        mCategoryOtherServiceEnabled = true;    // support other category
 
     }
 
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e0427..ce4f777 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,17 @@
     description: "Enable NFC Polling Loop Notifications ST shim"
     bug: "294217286"
 }
+
+flag {
+    name: "enable_tag_detection_broadcasts"
+    namespace: "nfc"
+    description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+    bug: "306203494"
+}
+
+flag {
+    name: "enable_nfc_charging"
+    namespace: "nfc"
+    description: "Flag for NFC charging changes"
+    bug: "292143899"
+}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 209a595..d3f2c7a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -92,3 +92,6 @@
 per-file IThermal* = file:/THERMAL_OWNERS
 per-file CoolingDevice.java = file:/THERMAL_OWNERS
 per-file Temperature.java = file:/THERMAL_OWNERS
+
+# SecurityStateManager
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523e..80ec458 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -757,7 +757,6 @@
                                                   @Nullable String invokeWith,
                                                   @Nullable String packageName,
                                                   @Nullable long[] disabledCompatChanges,
-                                                  boolean bindMountSyspropOverrides,
                                                   @Nullable String[] zygoteArgs) {
         // Webview zygote can't access app private data files, so doesn't need to know its data
         // info.
@@ -767,7 +766,7 @@
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
                 disabledCompatChanges, /* pkgDataInfoMap */ null,
                 /* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
-                /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
+                /* bindMountAppStorageDirs */ false, /* bindMountSyspropOverrides */ false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40b..df503e8 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 /**
  * An exception specific to a service.
@@ -33,6 +34,7 @@
  * @hide
  */
 @SystemApi
+@RavenwoodKeepWholeClass
 public class ServiceSpecificException extends RuntimeException {
     public final int errorCode;
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4af657d..54cc5f4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14339,6 +14339,14 @@
         public static final String MODE_RINGER = "mode_ringer";
 
         /**
+         * Whether or not Alarm stream should always be muted with Ringer.
+         *
+         * @hide
+         */
+        public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
+                "mute_alarm_stream_with_ringer_mode";
+
+        /**
          * Overlay display devices setting.
          * The associated value is a specially formatted string that describes the
          * size and density of simulated secondary display devices.
@@ -15013,6 +15021,16 @@
                 "foreground_service_starts_logging_enabled";
 
         /**
+         * Describes whether AM's AppProfiler should collect PSS even if RSS is the default. This
+         * can be set by a user in developer settings.
+         * Default: 0
+         * @hide
+         */
+        @Readable
+        public static final String FORCE_ENABLE_PSS_PROFILING =
+                "force_enable_pss_profiling";
+
+        /**
          * @hide
          * @see com.android.server.appbinding.AppBindingConstants
          */
@@ -19620,6 +19638,15 @@
              */
             public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED =
                     "wear_power_anomaly_service_enabled";
+
+            /**
+             * A boolean that tracks whether Wrist Detection Auto-Locking is enabled.
+             *
+             * @hide
+             */
+            @Readable
+            public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
+                    "wear_wrist_detection_auto_locking_enabled";
         }
     }
 
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3..30524a1 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@
     description: "Collect sepolicy hash from sysfs"
     bug: "308471499"
 }
+
+flag {
+    name: "frp_enforcement"
+    namespace: "android_hw_security"
+    description: "This flag controls whether PDB enforces FRP"
+    bug: "290312729"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index cabab6c..0927d45 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,7 +17,6 @@
 package android.view;
 
 import static android.view.InsetsSourceProto.FRAME;
-import static android.view.InsetsSourceProto.TYPE;
 import static android.view.InsetsSourceProto.TYPE_NUMBER;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -353,13 +352,12 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(TYPE, WindowInsets.Type.toString(mType));
-        proto.write(TYPE_NUMBER, mType);
         mFrame.dumpDebug(proto, FRAME);
         if (mVisibleFrame != null) {
             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
         }
         proto.write(VISIBLE, mVisible);
+        proto.write(TYPE_NUMBER, mType);
         proto.end(token);
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 34b2884..0ce61bb 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -21,11 +21,11 @@
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
 import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
-import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE;
 import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE;
 import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
 import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
 import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
+import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -393,7 +393,6 @@
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType));
         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
         proto.write(IS_REQUESTED_VISIBLE, isShowRequested());
         if (mSourceControl != null) {
@@ -406,6 +405,7 @@
             mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
         }
         proto.write(ANIMATION_STATE, mAnimationState);
+        proto.write(TYPE_NUMBER, mType);
         proto.end(token);
     }
 }
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7ea93f5..527c7ed 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -20,7 +20,7 @@
 import static android.graphics.PointProto.Y;
 import static android.view.InsetsSourceControlProto.LEASH;
 import static android.view.InsetsSourceControlProto.POSITION;
-import static android.view.InsetsSourceControlProto.TYPE;
+import static android.view.InsetsSourceControlProto.TYPE_NUMBER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -244,8 +244,6 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(TYPE, WindowInsets.Type.toString(mType));
-
         final long surfaceToken = proto.start(POSITION);
         proto.write(X, mSurfacePosition.x);
         proto.write(Y, mSurfacePosition.y);
@@ -254,6 +252,8 @@
         if (mLeash != null) {
             mLeash.dumpDebug(proto, LEASH);
         }
+
+        proto.write(TYPE_NUMBER, mType);
         proto.end(token);
     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cbbe785..b957b31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
@@ -38,7 +37,6 @@
 import android.annotation.Size;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
@@ -53,13 +51,8 @@
 import android.hardware.OverlayProperties;
 import android.hardware.SyncFence;
 import android.hardware.display.DeviceProductInfo;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplay;
 import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSync;
@@ -68,8 +61,6 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -2355,92 +2346,6 @@
     }
 
     /**
-     * Because this API is now going through {@link DisplayManager}, orientation and displayRect
-     * will automatically be computed based on configuration changes. Because of this, the params
-     * orientation and displayRect are ignored
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.",
-            trackingBug = 247078497)
-    public static void setDisplayProjection(IBinder displayToken, int orientation,
-            Rect layerStackRect, Rect displayRect) {
-        DisplayManagerGlobal.getInstance().resizeVirtualDisplay(
-                IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(),
-                layerStackRect.height(), 1);
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag "
-                    + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.",
-            trackingBug = 247078497)
-    public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
-        IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
-        if (b == null) {
-            throw new UnsupportedOperationException();
-        }
-
-        IDisplayManager dm = IDisplayManager.Stub.asInterface(b);
-        try {
-            dm.setDisplayIdToMirror(displayToken, layerStack);
-        } catch (RemoteException e) {
-            throw new UnsupportedOperationException(e);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.",
-            trackingBug = 247078497)
-    public static void setDisplaySurface(IBinder displayToken, Surface surface) {
-        IVirtualDisplayCallback virtualDisplayCallback =
-                IVirtualDisplayCallback.Stub.asInterface(displayToken);
-        DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-        dm.setVirtualDisplaySurface(virtualDisplayCallback, surface);
-    }
-
-    /**
-     * Secure is no longer supported because this is only called from outside system which cannot
-     * create secure displays.
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or "
-                    + "{@code DisplayManager#createVirtualDisplay()} instead.",
-            trackingBug = 247078497)
-    public static IBinder createDisplay(String name, boolean secure) {
-        if (name == null) {
-            throw new IllegalArgumentException("name must not be null");
-        }
-
-        // We don't have a size yet so pass in 1 for width and height since 0 is invalid
-        VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
-                INVALID_DISPLAY, null /* Surface */);
-        return vd == null ? null : vd.getToken().asBinder();
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#release()} instead.",
-            trackingBug = 247078497)
-    public static void destroyDisplay(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        DisplayManagerGlobal.getInstance().releaseVirtualDisplay(
-                IVirtualDisplayCallback.Stub.asInterface(displayToken));
-    }
-
-    /**
      * Returns whether protected content is supported in GPU composition.
      * @hide
      */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e4b709e..ec99459 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32,6 +32,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.flags.Flags.viewVelocityApi;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -2309,6 +2310,7 @@
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
 
     static {
         EMPTY_STATE_SET = StateSet.get(0);
@@ -2393,6 +2395,7 @@
                         | StateSet.VIEW_STATE_PRESSED);
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+        sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
     }
 
     /**
@@ -33084,11 +33087,11 @@
 
     private void votePreferredFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
-        if (sToolkitSetFrameRateReadOnlyFlagValue) {
-            ViewRootImpl viewRootImpl = getViewRootImpl();
-            float sizePercentage = getSizePercentage();
-            int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
-            if (viewRootImpl != null && sizePercentage > 0) {
+        ViewRootImpl viewRootImpl = getViewRootImpl();
+        float sizePercentage = getSizePercentage();
+        int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
+        if (viewRootImpl != null && sizePercentage > 0) {
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
                 if (mPreferredFrameRate < 0) {
                     if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
                         frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -33104,6 +33107,9 @@
                 }
                 viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
             }
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                viewRootImpl.recordViewPercentage(sizePercentage);
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1853282..1f81a64 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -235,6 +235,7 @@
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
 import com.android.internal.view.SurfaceCallbackHelper;
+import com.android.modules.expresslog.Counter;
 import com.android.window.flags.Flags;
 
 import java.io.IOException;
@@ -827,6 +828,8 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
+    // The largest view size percentage to the display size. Used on trace to collect metric.
+    private float mLargestChildPercentage = 0.0f;
 
     /**
      * The resolved pointer icon type requested by this window.
@@ -1066,6 +1069,7 @@
 
     private String mTag = TAG;
     private String mFpsTraceName;
+    private String mLargestViewTraceName;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1317,6 +1321,7 @@
                 attrs = mWindowAttributes;
                 setTag();
                 mFpsTraceName = "FPS of " + getTitle();
+                mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle();
 
                 if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                         & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -4738,6 +4743,10 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
+
+        long percentage = (long) (mLargestChildPercentage * 100);
+        Trace.setCounter(mLargestViewTraceName, percentage);
+        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -5058,6 +5067,7 @@
         if (DEBUG_FPS) {
             trackFPS();
         }
+
         if (sToolkitMetricsForFrameRateDecisionFlagValue) {
             collectFrameRateDecisionMetrics();
         }
@@ -9695,6 +9705,9 @@
             } else {
                 q.mReceiver.finishInputEvent(q.mEvent, handled);
             }
+            if (q.mEvent instanceof KeyEvent) {
+                logHandledSystemKey((KeyEvent) q.mEvent, handled);
+            }
         } else {
             q.mEvent.recycleIfNeededAfterDispatch();
         }
@@ -9702,6 +9715,19 @@
         recycleQueuedInputEvent(q);
     }
 
+    private void logHandledSystemKey(KeyEvent event, boolean handled) {
+        final int keyCode = event.getKeyCode();
+        if (keyCode != KeyEvent.KEYCODE_STEM_PRIMARY) {
+            return;
+        }
+        if (event.isDown() && event.getRepeatCount() == 0 && handled) {
+            // Initial DOWN event is handled. Log the stem primary key press.
+            Counter.logIncrementWithUid(
+                    "input.value_app_handled_stem_primary_key_gestures_count",
+                    Process.myUid());
+        }
+    }
+
     static boolean isTerminalInputEvent(InputEvent event) {
         if (event instanceof KeyEvent) {
             final KeyEvent keyEvent = (KeyEvent)event;
@@ -12261,4 +12287,10 @@
     void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
         mWindowlessBackKeyCallback = callback;
     }
+
+    void recordViewPercentage(float percentage) {
+        if (!Trace.isEnabled()) return;
+        // Record the largest view of percentage to the display size.
+        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+    }
 }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 57a4161..921afaa 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -38,6 +38,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.graphics.Insets;
@@ -1519,6 +1521,9 @@
         }
 
         /** @hide */
+        @TestApi
+        @NonNull
+        @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
         public static String toString(@InsetsType int types) {
             StringBuilder result = new StringBuilder();
             if ((types & STATUS_BARS) != 0) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 53aed49..49a2843 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -5400,13 +5400,10 @@
         public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content forward.
          *
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
          * this element should also add the relevant directional scroll actions of
@@ -5447,12 +5444,10 @@
         public static final AccessibilityAction ACTION_SCROLL_FORWARD =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content backward.
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
+         *
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
          * this element should also add the relevant directional scroll actions of
@@ -5647,48 +5642,40 @@
         @NonNull public static final AccessibilityAction ACTION_SCROLL_IN_DIRECTION =
                 new AccessibilityAction(R.id.accessibilityActionScrollInDirection);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content up.
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
+         *
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_UP =
                 new AccessibilityAction(R.id.accessibilityActionScrollUp);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content left.
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
+         *
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_LEFT =
                 new AccessibilityAction(R.id.accessibilityActionScrollLeft);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content down.
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
+         *
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_DOWN =
                 new AccessibilityAction(R.id.accessibilityActionScrollDown);
 
+        // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
         /**
          * Action to scroll the node content right.
-         * <p>
-         *     <strong>Arguments:</strong>
-         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
-         * </p>
+         *
          * <p>The UI element that implements this should send a
          * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index b3359b7..70d8abe 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -23,9 +23,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.InterpolatorRes;
 import android.annotation.TestApi;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -55,18 +52,6 @@
     private static final int TOGETHER = 0;
     private static final int SEQUENTIALLY = 1;
 
-     /**
-     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
-     * this change ID enables to use expectedPresentationTime instead of the frameTime
-     * for the frame start time .
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    @Overridable
-    public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
-
     private static boolean sExpectedPresentationTimeFlagValue;
     static {
         sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ac9ad2d..feccc6b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2320,6 +2320,15 @@
      * @hide
      */
     public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+        final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
+        synchronized (mH) {
+            if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+                // Fail early if the view is not focused and not served
+                // to avoid logging many erroneous calls.
+                return false;
+            }
+        }
+
         final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
         final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
                 null /* component */, Process.myUid(),
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c7609a6..828ec26 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -44,9 +44,7 @@
     static final String PROXY_BASE = "file:///cookieless_proxy/";
     static final String CONTENT_BASE = "content:";
 
-    /**
-     * Cleans up (if possible) user-entered web addresses
-     */
+    /** Cleans up (if possible) user-entered web addresses */
     public static String guessUrl(String inUrl) {
 
         String retVal = inUrl;
@@ -86,8 +84,12 @@
         return webAddress.toString();
     }
 
-    public static String composeSearchUrl(String inQuery, String template,
-                                          String queryPlaceHolder) {
+    /**
+     * Inserts the {@code inQuery} in the {@code template} after URL-encoding it. The encoded query
+     * will replace the {@code queryPlaceHolder}.
+     */
+    public static String composeSearchUrl(
+            String inQuery, String template, String queryPlaceHolder) {
         int placeHolderIndex = template.indexOf(queryPlaceHolder);
         if (placeHolderIndex < 0) {
             return null;
@@ -104,8 +106,7 @@
             return null;
         }
 
-        buffer.append(template.substring(
-                placeHolderIndex + queryPlaceHolder.length()));
+        buffer.append(template.substring(placeHolderIndex + queryPlaceHolder.length()));
 
         return buffer.toString();
     }
@@ -123,8 +124,7 @@
             byte b = url[i];
             if (b == '%') {
                 if (url.length - i > 2) {
-                    b = (byte) (parseHex(url[i + 1]) * 16
-                            + parseHex(url[i + 2]));
+                    b = (byte) (parseHex(url[i + 1]) * 16 + parseHex(url[i + 2]));
                     i += 2;
                 } else {
                     throw new IllegalArgumentException("Invalid format");
@@ -189,8 +189,8 @@
     }
 
     /**
-     * @return {@code true} if the url is a proxy url to allow cookieless network
-     * requests from a file url.
+     * @return {@code true} if the url is a proxy url to allow cookieless network requests from a
+     *     file url.
      * @deprecated Cookieless proxy is no longer supported.
      */
     @Deprecated
@@ -202,9 +202,10 @@
      * @return {@code true} if the url is a local file.
      */
     public static boolean isFileUrl(String url) {
-        return (null != url) && (url.startsWith(FILE_BASE) &&
-                                 !url.startsWith(ASSET_BASE) &&
-                                 !url.startsWith(PROXY_BASE));
+        return (null != url)
+                && (url.startsWith(FILE_BASE)
+                        && !url.startsWith(ASSET_BASE)
+                        && !url.startsWith(PROXY_BASE));
     }
 
     /**
@@ -232,18 +233,18 @@
      * @return {@code true} if the url is an http: url.
      */
     public static boolean isHttpUrl(String url) {
-        return (null != url) &&
-               (url.length() > 6) &&
-               url.substring(0, 7).equalsIgnoreCase("http://");
+        return (null != url)
+                && (url.length() > 6)
+                && url.substring(0, 7).equalsIgnoreCase("http://");
     }
 
     /**
      * @return {@code true} if the url is an https: url.
      */
     public static boolean isHttpsUrl(String url) {
-        return (null != url) &&
-               (url.length() > 7) &&
-               url.substring(0, 8).equalsIgnoreCase("https://");
+        return (null != url)
+                && (url.length() > 7)
+                && url.substring(0, 8).equalsIgnoreCase("https://");
     }
 
     /**
@@ -271,19 +272,17 @@
             return false;
         }
 
-        return (isAssetUrl(url) ||
-                isResourceUrl(url) ||
-                isFileUrl(url) ||
-                isAboutUrl(url) ||
-                isHttpUrl(url) ||
-                isHttpsUrl(url) ||
-                isJavaScriptUrl(url) ||
-                isContentUrl(url));
+        return (isAssetUrl(url)
+                || isResourceUrl(url)
+                || isFileUrl(url)
+                || isAboutUrl(url)
+                || isHttpUrl(url)
+                || isHttpsUrl(url)
+                || isJavaScriptUrl(url)
+                || isContentUrl(url));
     }
 
-    /**
-     * Strips the url of the anchor.
-     */
+    /** Strips the url of the anchor. */
     public static String stripAnchor(String url) {
         int anchorIndex = url.indexOf('#');
         if (anchorIndex != -1) {
@@ -293,19 +292,16 @@
     }
 
     /**
-     * Guesses canonical filename that a download would have, using
-     * the URL and contentDisposition. File extension, if not defined,
-     * is added based on the mimetype
+     * Guesses canonical filename that a download would have, using the URL and contentDisposition.
+     * File extension, if not defined, is added based on the mimetype
+     *
      * @param url Url to the content
      * @param contentDisposition Content-Disposition HTTP header or {@code null}
      * @param mimeType Mime-type of the content or {@code null}
-     *
      * @return suggested filename
      */
     public static final String guessFileName(
-            String url,
-            @Nullable String contentDisposition,
-            @Nullable String mimeType) {
+            String url, @Nullable String contentDisposition, @Nullable String mimeType) {
         String filename = null;
         String extension = null;
 
@@ -369,8 +365,9 @@
                 // Compare the last segment of the extension against the mime type.
                 // If there's a mismatch, discard the entire extension.
                 int lastDotIndex = filename.lastIndexOf('.');
-                String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
-                        filename.substring(lastDotIndex + 1));
+                String typeFromExt =
+                        MimeTypeMap.getSingleton()
+                                .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
                 if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
                     extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
                     if (extension != null) {
@@ -389,17 +386,17 @@
 
     /** Regex used to parse content-disposition headers */
     private static final Pattern CONTENT_DISPOSITION_PATTERN =
-            Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
-            Pattern.CASE_INSENSITIVE);
+            Pattern.compile(
+                    "attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+                    Pattern.CASE_INSENSITIVE);
 
     /**
-     * Parse the Content-Disposition HTTP Header. The format of the header
-     * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
-     * This header provides a filename for content that is going to be
-     * downloaded to the file system. We only support the attachment type.
-     * Note that RFC 2616 specifies the filename value must be double-quoted.
-     * Unfortunately some servers do not quote the value so to maintain
-     * consistent behaviour with other browsers, we allow unquoted values too.
+     * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
+     * content that is going to be downloaded to the file system. We only support the attachment
+     * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately
+     * some servers do not quote the value so to maintain consistent behaviour with other browsers,
+     * we allow unquoted values too.
      */
     @UnsupportedAppUsage
     static String parseContentDisposition(String contentDisposition) {
@@ -409,7 +406,7 @@
                 return m.group(2);
             }
         } catch (IllegalStateException ex) {
-             // This function is defined as returning null when it can't parse the header
+            // This function is defined as returning null when it can't parse the header
         }
         return null;
     }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
      * changes to this setting after that point.
      *
      * @param flag {@code true} if the WebView should use the database storage API
+     * @deprecated WebSQL is deprecated and this method will become a no-op on all
+     * Android versions once support is removed in Chromium. See
+     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
+    @Deprecated
     public abstract void setDatabaseEnabled(boolean flag);
 
     /**
@@ -1236,7 +1240,11 @@
      *
      * @return {@code true} if the database storage API is enabled
      * @see #setDatabaseEnabled
+     * @deprecated WebSQL is deprecated and this method will become a no-op on all
+     * Android versions once support is removed in Chromium. See
+     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
+    @Deprecated
     public abstract boolean getDatabaseEnabled();
 
     /**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5bfa3d7..7c9340e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -165,6 +165,9 @@
     public static final int FLAGS_IS_NON_APP_WINDOW =
             FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
 
+    /** The change will not participate in the animation. */
+    public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
+
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 933cc49..59d7b0e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -2,13 +2,6 @@
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
 
-flag {
-    namespace: "windowing_sdk"
-    name: "sync_window_config_update_flag"
-    description: "Whether the feature to sync different window-related config updates is enabled"
-    bug: "260873529"
-}
-
 # Using a fixed read only flag because there are ClientTransaction scheduling before
 # WindowManagerService creation.
 flag {
@@ -35,13 +28,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "window_state_resize_item_flag"
-    description: "Whether to dispatch window resize through ClientTransaction is enabled"
-    bug: "301870955"
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "fullscreen_dim_flag"
     description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
     bug: "253533308"
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d29..7dcbbea 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@
 
     private UserHandle mCloneProfileUserHandle;
     private UserHandle mTabOwnerUserHandleForLaunch;
+    private UserHandle mPrivateProfileUserHandle;
 
     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
 
@@ -441,6 +442,7 @@
         mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
         mWorkProfileUserHandle = fetchWorkProfileUserProfile();
         mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+        mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
         mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
 
         // The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@
                 initialIntents,
                 rList,
                 filterLastUsed,
-                /* userHandle */ getPersonalProfileUserHandle());
+                getPersonalProfileUserHandle());
+
         QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
@@ -747,6 +750,9 @@
     }
 
     protected UserHandle getPersonalProfileUserHandle() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+            return mPrivateProfileUserHandle;
+        }
         return mPersonalProfileUserHandle;
     }
     protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@
         return mTabOwnerUserHandleForLaunch;
     }
 
+    protected UserHandle getPrivateProfileUserHandle() {
+        return mPrivateProfileUserHandle;
+    }
+
     protected UserHandle fetchPersonalProfileUserHandle() {
         // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
         // profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@
         return mCloneProfileUserHandle;
     }
 
+    protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+        mPrivateProfileUserHandle = null;
+        UserManager userManager = getSystemService(UserManager.class);
+        for (final UserInfo userInfo :
+                userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+            if (userInfo.isPrivateProfile()) {
+                mPrivateProfileUserHandle = userInfo.getUserHandle();
+                break;
+            }
+        }
+        return mPrivateProfileUserHandle;
+    }
+
     private UserHandle fetchTabOwnerUserHandleForLaunch() {
-        // If we are in work profile's process, return WorkProfile user as owner, otherwise we
-        // always return PersonalProfile user as owner
-        return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
-                ? getWorkProfileUserHandle()
-                : getPersonalProfileUserHandle();
+        // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+        // as owner, otherwise we always return PersonalProfile user as owner
+        if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+            return getWorkProfileUserHandle();
+        } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return getPrivateProfileUserHandle();
+        }
+        return getPersonalProfileUserHandle();
     }
 
     private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@
                 && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
     }
 
+    protected final boolean isLaunchedAsPrivateProfile() {
+        return getPrivateProfileUserHandle() != null
+                && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+    }
+
     protected boolean shouldShowTabs() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return false;
+        }
         return hasWorkProfile() && ENABLE_TABBED_VIEW;
     }
 
@@ -2619,6 +2653,11 @@
         return resolveInfo.userHandle;
     }
 
+    private boolean privateSpaceEnabled() {
+        return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+    }
+
     /**
      * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
      * invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4..5705b7e 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 
+import java.lang.ref.WeakReference;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -63,6 +64,8 @@
 
     PackageMonitorCallback mPackageMonitorCallback;
 
+    private Executor mExecutor;
+
     @UnsupportedAppUsage
     public PackageMonitor() {
         final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@
         if (mPackageMonitorCallback == null) {
             PackageManager pm = mRegisteredContext.getPackageManager();
             if (pm != null) {
-                mPackageMonitorCallback = new PackageMonitorCallback(this,
-                        new HandlerExecutor(mRegisteredHandler));
+                mExecutor = new HandlerExecutor(mRegisteredHandler);
+                mPackageMonitorCallback = new PackageMonitorCallback(this);
                 int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
                 pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
             }
@@ -131,6 +134,7 @@
         }
         mPackageMonitorCallback = null;
         mRegisteredContext = null;
+        mExecutor = null;
     }
 
     public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@
         doHandlePackageEvent(intent);
     }
 
+
+    private void postHandlePackageEvent(Intent intent) {
+        if (mExecutor != null) {
+            mExecutor.execute(() -> doHandlePackageEvent(intent));
+        }
+    }
+
     /**
      * Handle the package related event
      * @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@
     }
 
     private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+        private final WeakReference<PackageMonitor> mMonitorWeakReference;
 
-        private final PackageMonitor mPackageMonitor;
-        private final Executor mExecutor;
-
-        PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
-            mPackageMonitor = monitor;
-            mExecutor = executor;
+        PackageMonitorCallback(PackageMonitor monitor) {
+            mMonitorWeakReference = new WeakReference<>(monitor);
         }
 
         @Override
@@ -537,7 +545,10 @@
                 Log.w(TAG, "No intent is set for PackageMonitorCallback");
                 return;
             }
-            mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+            PackageMonitor monitor = mMonitorWeakReference.get();
+            if (monitor != null) {
+                monitor.postHandlePackageEvent(intent);
+            }
         }
     }
 }
diff --git a/core/java/com/android/internal/pm/parsing/IPackageCacher.java b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
new file mode 100644
index 0000000..3e01730
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+
+import java.io.File;
+
+/** @hide */
+public interface IPackageCacher {
+
+    /**
+     * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
+     * or {@code null} if no cached result exists.
+     */
+    ParsedPackage getCachedResult(File packageFile, int flags);
+
+    /**
+     * Caches the parse result for {@code packageFile} with flags {@code flags}.
+     */
+    void cacheResult(File packageFile, int flags, ParsedPackage parsed);
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
new file mode 100644
index 0000000..f05d9cb
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+import static com.android.internal.pm.pkg.SEInfoUtil.COMPLETE_STR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FallbackCategoryProvider;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.os.Debug;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import java.util.List;
+
+/**
+ * Method that use a {@link AndroidPackage} to generate a {@link PackageInfo} though
+ * the given {@link PackageManager.PackageInfoFlags}
+ * @hide
+ **/
+// TODO(b/317215254): refactor coped code from PackageInfoUtils
+public class PackageInfoCommonUtils {
+
+    private static final String TAG = ParsingUtils.TAG;
+    private static final boolean DEBUG = false;
+
+    /**
+     * Generates a {@link PackageInfo} from the given {@link AndroidPackage}
+     */
+    @Nullable
+    public static PackageInfo generate(@Nullable AndroidPackage pkg,
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+        if (pkg == null) {
+            return null;
+        }
+        ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, userId);
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = pkg.getPackageName();
+        info.splitNames = pkg.getSplitNames();
+        info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
+        info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
+        info.baseRevisionCode = pkg.getBaseRevisionCode();
+        info.splitRevisionCodes = pkg.getSplitRevisionCodes();
+        info.versionName = pkg.getVersionName();
+        info.sharedUserId = pkg.getSharedUserId();
+        info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+        info.applicationInfo = applicationInfo;
+        info.installLocation = pkg.getInstallLocation();
+        if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                || (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+            info.requiredForAllUsers = pkg.isRequiredForAllUsers();
+        }
+        info.restrictedAccountType = pkg.getRestrictedAccountType();
+        info.requiredAccountType = pkg.getRequiredAccountType();
+        info.overlayTarget = pkg.getOverlayTarget();
+        info.targetOverlayableName = pkg.getOverlayTargetOverlayableName();
+        info.overlayCategory = pkg.getOverlayCategory();
+        info.overlayPriority = pkg.getOverlayPriority();
+        info.mOverlayIsStatic = pkg.isOverlayIsStatic();
+        info.compileSdkVersion = pkg.getCompileSdkVersion();
+        info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
+        info.isStub = pkg.isStub();
+        info.coreApp = pkg.isCoreApp();
+        info.isApex = pkg.isApex();
+
+        if ((flags & PackageManager.GET_CONFIGURATIONS) != 0) {
+            int size = pkg.getConfigPreferences().size();
+            if (size > 0) {
+                info.configPreferences = new ConfigurationInfo[size];
+                pkg.getConfigPreferences().toArray(info.configPreferences);
+            }
+            size = pkg.getRequestedFeatures().size();
+            if (size > 0) {
+                info.reqFeatures = new FeatureInfo[size];
+                pkg.getRequestedFeatures().toArray(info.reqFeatures);
+            }
+            size = pkg.getFeatureGroups().size();
+            if (size > 0) {
+                info.featureGroups = new FeatureGroupInfo[size];
+                pkg.getFeatureGroups().toArray(info.featureGroups);
+            }
+        }
+        if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
+            int size = ArrayUtils.size(pkg.getPermissions());
+            if (size > 0) {
+                info.permissions = new PermissionInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final var permission = pkg.getPermissions().get(i);
+                    final var permissionInfo = generatePermissionInfo(permission, flags);
+                    info.permissions[i] = permissionInfo;
+                }
+            }
+            final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+            size = usesPermissions.size();
+            if (size > 0) {
+                info.requestedPermissions = new String[size];
+                info.requestedPermissionsFlags = new int[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+                    info.requestedPermissions[i] = usesPermission.getName();
+                    // The notion of required permissions is deprecated but for compatibility.
+                    info.requestedPermissionsFlags[i] |=
+                            PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+                    if ((usesPermission.getUsesPermissionFlags()
+                            & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0) {
+                        info.requestedPermissionsFlags[i] |=
+                                PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+                    }
+                    if (pkg.getImplicitPermissions().contains(info.requestedPermissions[i])) {
+                        info.requestedPermissionsFlags[i] |=
+                                PackageInfo.REQUESTED_PERMISSION_IMPLICIT;
+                    }
+                }
+            }
+        }
+        if ((flags & PackageManager.GET_ATTRIBUTIONS_LONG) != 0) {
+            int size = ArrayUtils.size(pkg.getAttributions());
+            if (size > 0) {
+                info.attributions = new Attribution[size];
+                for (int i = 0; i < size; i++) {
+                    ParsedAttribution parsedAttribution = pkg.getAttributions().get(i);
+                    if (parsedAttribution != null) {
+                        info.attributions[i] = new Attribution(parsedAttribution.getTag(),
+                                parsedAttribution.getLabel());
+                    }
+                }
+            }
+            if (pkg.isAttributionsUserVisible()) {
+                info.applicationInfo.privateFlagsExt
+                        |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+            } else {
+                info.applicationInfo.privateFlagsExt
+                        &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+            }
+        } else {
+            info.applicationInfo.privateFlagsExt
+                    &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+        }
+
+        final SigningDetails signingDetails = pkg.getSigningDetails();
+        // deprecated method of getting signing certificates
+        if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+            if (signingDetails.hasPastSigningCertificates()) {
+                // Package has included signing certificate rotation information.  Return the oldest
+                // cert so that programmatic checks keep working even if unaware of key rotation.
+                info.signatures = new Signature[1];
+                info.signatures[0] = signingDetails.getPastSigningCertificates()[0];
+            } else if (signingDetails.hasSignatures()) {
+                // otherwise keep old behavior
+                int numberOfSigs = signingDetails.getSignatures().length;
+                info.signatures = new Signature[numberOfSigs];
+                System.arraycopy(signingDetails.getSignatures(), 0, info.signatures, 0,
+                        numberOfSigs);
+            }
+        }
+
+        // replacement for GET_SIGNATURES
+        if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+            if (signingDetails != SigningDetails.UNKNOWN) {
+                // only return a valid SigningInfo if there is signing information to report
+                info.signingInfo = new SigningInfo(signingDetails);
+            } else {
+                info.signingInfo = null;
+            }
+        }
+
+        if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+            final int size = pkg.getActivities().size();
+            if (size > 0) {
+                int num = 0;
+                final ActivityInfo[] res = new ActivityInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedActivity a = pkg.getActivities().get(i);
+                    if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+                        if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
+                                a.getName())) {
+                            continue;
+                        }
+                        res[num++] = generateActivityInfo(a, flags, applicationInfo);
+                    }
+                }
+                info.activities = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+            final int size = pkg.getReceivers().size();
+            if (size > 0) {
+                int num = 0;
+                final ActivityInfo[] res = new ActivityInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedActivity a = pkg.getReceivers().get(i);
+                    if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+                        res[num++] = generateActivityInfo(a, flags, applicationInfo);
+                    }
+                }
+                info.receivers = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_SERVICES) != 0) {
+            final int size = pkg.getServices().size();
+            if (size > 0) {
+                int num = 0;
+                final ServiceInfo[] res = new ServiceInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedService s = pkg.getServices().get(i);
+                    if (isMatch(pkg, s.isDirectBootAware(), flags)) {
+                        res[num++] = generateServiceInfo(s, flags, applicationInfo);
+                    }
+                }
+                info.services = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+            final int size = pkg.getProviders().size();
+            if (size > 0) {
+                int num = 0;
+                final ProviderInfo[] res = new ProviderInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedProvider pr = pkg.getProviders().get(i);
+                    if (isMatch(pkg, pr.isDirectBootAware(), flags)) {
+                        res[num++] = generateProviderInfo(pkg, pr, flags, applicationInfo, userId);
+                    }
+                }
+                info.providers = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_INSTRUMENTATION) != 0) {
+            final int size = pkg.getInstrumentations().size();
+            if (size > 0) {
+                info.instrumentation = new InstrumentationInfo[size];
+                for (int i = 0; i < size; i++) {
+                    info.instrumentation[i] = generateInstrumentationInfo(
+                            pkg.getInstrumentations().get(i), pkg, flags, userId);
+                }
+            }
+        }
+
+        return info;
+    }
+
+    private static void updateApplicationInfo(ApplicationInfo ai, long flags) {
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            ai.metaData = null;
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) == 0) {
+            ai.sharedLibraryFiles = null;
+            ai.sharedLibraryInfos = null;
+        }
+
+        // CompatibilityMode is global state.
+        if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
+            ai.disableCompatibilityMode();
+        }
+
+        if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+        }
+        ai.seInfoUser = COMPLETE_STR;
+    }
+
+    @Nullable
+    private static ApplicationInfo generateApplicationInfo(@NonNull AndroidPackage pkg,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId) {
+
+        // Make shallow copy so we can store the metadata/libraries safely
+        ApplicationInfo info = ((AndroidPackageHidden) pkg).toAppInfoWithoutState();
+
+        updateApplicationInfo(info, flags);
+
+        initForUser(info, pkg, userId);
+
+        info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+        info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            info.metaData = pkg.getMetaData();
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+            List<String> usesLibraryFiles = pkg.getUsesLibraries();
+
+            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+                    ? null : usesLibraryFiles.toArray(new String[0]);
+        }
+
+        return info;
+    }
+
+    @Nullable
+    private static ActivityInfo generateActivityInfo(ParsedActivity a,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo) {
+        if (a == null) return null;
+
+        // Make shallow copies so we can store the metadata safely
+        ActivityInfo ai = new ActivityInfo();
+        ai.targetActivity = a.getTargetActivity();
+        ai.processName = a.getProcessName();
+        ai.exported = a.isExported();
+        ai.theme = a.getTheme();
+        ai.uiOptions = a.getUiOptions();
+        ai.parentActivityName = a.getParentActivityName();
+        ai.permission = a.getPermission();
+        ai.taskAffinity = a.getTaskAffinity();
+        ai.flags = a.getFlags();
+        ai.privateFlags = a.getPrivateFlags();
+        ai.launchMode = a.getLaunchMode();
+        ai.documentLaunchMode = a.getDocumentLaunchMode();
+        ai.maxRecents = a.getMaxRecents();
+        ai.configChanges = a.getConfigChanges();
+        ai.softInputMode = a.getSoftInputMode();
+        ai.persistableMode = a.getPersistableMode();
+        ai.lockTaskLaunchMode = a.getLockTaskLaunchMode();
+        ai.screenOrientation = a.getScreenOrientation();
+        ai.resizeMode = a.getResizeMode();
+        ai.setMaxAspectRatio(a.getMaxAspectRatio());
+        ai.setMinAspectRatio(a.getMinAspectRatio());
+        ai.supportsSizeChanges = a.isSupportsSizeChanges();
+        ai.requestedVrComponent = a.getRequestedVrComponent();
+        ai.rotationAnimation = a.getRotationAnimation();
+        ai.colorMode = a.getColorMode();
+        ai.windowLayout = a.getWindowLayout();
+        ai.attributionTags = a.getAttributionTags();
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = a.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            ai.metaData = metaData.isEmpty() ? null : metaData;
+        } else {
+            ai.metaData = null;
+        }
+        ai.applicationInfo = applicationInfo;
+        ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+        ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
+        assignFieldsComponentInfoParsedMainComponent(ai, a);
+        return ai;
+    }
+
+    @Nullable
+    private static ServiceInfo generateServiceInfo(ParsedService s,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo) {
+        if (s == null) return null;
+
+        // Make shallow copies so we can store the metadata safely
+        ServiceInfo si = new ServiceInfo();
+        si.exported = s.isExported();
+        si.flags = s.getFlags();
+        si.permission = s.getPermission();
+        si.processName = s.getProcessName();
+        si.mForegroundServiceType = s.getForegroundServiceType();
+        si.applicationInfo = applicationInfo;
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = s.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            si.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        assignFieldsComponentInfoParsedMainComponent(si, s);
+        return si;
+    }
+
+    @Nullable
+    private static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo, int userId) {
+        if (p == null) return null;
+
+        if (!pkg.getPackageName().equals(applicationInfo.packageName)) {
+            Slog.wtf(TAG, "AppInfo's package name is different. Expected=" + pkg.getPackageName()
+                    + " actual=" + applicationInfo.packageName);
+            applicationInfo = generateApplicationInfo(pkg, flags, userId);
+        }
+
+        // Make shallow copies so we can store the metadata safely
+        ProviderInfo pi = new ProviderInfo();
+        pi.exported = p.isExported();
+        pi.flags = p.getFlags();
+        pi.processName = p.getProcessName();
+        pi.authority = p.getAuthority();
+        pi.isSyncable = p.isSyncable();
+        pi.readPermission = p.getReadPermission();
+        pi.writePermission = p.getWritePermission();
+        pi.grantUriPermissions = p.isGrantUriPermissions();
+        pi.forceUriPermissions = p.isForceUriPermissions();
+        pi.multiprocess = p.isMultiProcess();
+        pi.initOrder = p.getInitOrder();
+        pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+        pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
+        if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+            pi.uriPermissionPatterns = null;
+        }
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = p.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            pi.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        pi.applicationInfo = applicationInfo;
+        assignFieldsComponentInfoParsedMainComponent(pi, p);
+        return pi;
+    }
+
+    @Nullable
+    private static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+            AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
+        if (i == null) return null;
+
+        InstrumentationInfo info = new InstrumentationInfo();
+        info.targetPackage = i.getTargetPackage();
+        info.targetProcesses = i.getTargetProcesses();
+        info.handleProfiling = i.isHandleProfiling();
+        info.functionalTest = i.isFunctionalTest();
+
+        info.sourceDir = pkg.getBaseApkPath();
+        info.publicSourceDir = pkg.getBaseApkPath();
+        info.splitNames = pkg.getSplitNames();
+        info.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+        info.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+                ? null : pkg.getSplitCodePaths();
+        info.splitDependencies = pkg.getSplitDependencies().size() == 0
+                ? null : pkg.getSplitDependencies();
+
+        initForUser(info, pkg, userId);
+
+        info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+        info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+        info.nativeLibraryDir = pkg.getNativeLibraryDir();
+        info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
+
+        assignFieldsPackageItemInfoParsedComponent(info, i);
+
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            info.metaData = null;
+        } else {
+            var metaData = i.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            info.metaData = metaData.isEmpty() ? null : metaData;
+        }
+
+        return info;
+    }
+
+    @Nullable
+    private static PermissionInfo generatePermissionInfo(ParsedPermission p,
+            @PackageManager.ComponentInfoFlagsBits long flags) {
+        // TODO(b/135203078): Remove null checks and make all usages @NonNull
+        if (p == null) return null;
+
+        PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
+
+        assignFieldsPackageItemInfoParsedComponent(pi, p);
+
+        pi.group = p.getGroup();
+        pi.requestRes = p.getRequestRes();
+        pi.protectionLevel = p.getProtectionLevel();
+        pi.descriptionRes = p.getDescriptionRes();
+        pi.flags = p.getFlags();
+        pi.knownCerts = p.getKnownCerts();
+
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            pi.metaData = null;
+        } else {
+            var metaData = p.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            pi.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        return pi;
+    }
+
+    private static void assignFieldsComponentInfoParsedMainComponent(
+            @NonNull ComponentInfo info, @NonNull ParsedMainComponent component) {
+        assignFieldsPackageItemInfoParsedComponent(info, component);
+        info.descriptionRes = component.getDescriptionRes();
+        info.directBootAware = component.isDirectBootAware();
+        info.enabled = component.isEnabled();
+        info.splitName = component.getSplitName();
+        info.attributionTags = component.getAttributionTags();
+        info.nonLocalizedLabel = component.getNonLocalizedLabel();
+        info.icon = component.getIcon();
+    }
+
+    private static void assignFieldsPackageItemInfoParsedComponent(
+            @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component) {
+        packageItemInfo.nonLocalizedLabel = ComponentParseUtils.getNonLocalizedLabel(component);
+        packageItemInfo.icon = ComponentParseUtils.getIcon(component);
+        packageItemInfo.banner = component.getBanner();
+        packageItemInfo.labelRes = component.getLabelRes();
+        packageItemInfo.logo = component.getLogo();
+        packageItemInfo.name = component.getName();
+        packageItemInfo.packageName = component.getPackageName();
+    }
+
+    private static void initForUser(ApplicationInfo output, AndroidPackage input,
+            @UserIdInt int userId) {
+        PackageImpl pkg = ((PackageImpl) input);
+        String packageName = input.getPackageName();
+        output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
+
+        // For performance reasons, all these paths are built as strings
+        final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+        final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+        if (credentialDir !=  null && deviceDir != null) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                output.credentialProtectedDataDir = credentialDir + packageName;
+                output.deviceProtectedDataDir = deviceDir + packageName;
+            } else {
+                // Convert /data/user/0/ -> /data/user/1/com.example.app
+                String userIdString = String.valueOf(userId);
+                int credentialLength = credentialDir.length();
+                output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+                        .replace(credentialLength - 2, credentialLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+                int deviceLength = deviceDir.length();
+                output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+                        .replace(deviceLength - 2, deviceLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+            }
+        }
+
+        if (input.isDefaultToDeviceProtectedStorage()
+                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+            output.dataDir = output.deviceProtectedDataDir;
+        } else {
+            output.dataDir = output.credentialProtectedDataDir;
+        }
+    }
+
+    // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
+    // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
+    private static void initForUser(InstrumentationInfo output, AndroidPackage input,
+            @UserIdInt int userId) {
+        PackageImpl pkg = ((PackageImpl) input);
+        String packageName = input.getPackageName();
+
+        // For performance reasons, all these paths are built as strings
+        final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+        final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+        if (credentialDir !=  null && deviceDir != null) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                output.credentialProtectedDataDir = credentialDir + packageName;
+                output.deviceProtectedDataDir = deviceDir + packageName;
+            } else {
+                // Convert /data/user/0/ -> /data/user/1/com.example.app
+                String userIdString = String.valueOf(userId);
+                int credentialLength = credentialDir.length();
+                output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+                        .replace(credentialLength - 2, credentialLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+                int deviceLength = deviceDir.length();
+                output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+                        .replace(deviceLength - 2, deviceLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+            }
+        }
+
+        if (input.isDefaultToDeviceProtectedStorage()
+                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+            output.dataDir = output.deviceProtectedDataDir;
+        } else {
+            output.dataDir = output.credentialProtectedDataDir;
+        }
+    }
+
+    /**
+     * Test if the given component is considered system, enabled and a match for the given
+     * flags.
+     *
+     * <p>
+     * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
+     * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+     * </p>
+     */
+    private static boolean isMatch(AndroidPackage pkg,
+            boolean isComponentDirectBootAware, long flags) {
+        final boolean isSystem = ((AndroidPackageHidden) pkg).isSystem();
+        if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+            if (!isSystem) {
+                return reportIfDebug(false, flags);
+            }
+        }
+
+        final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0)
+                && !isComponentDirectBootAware;
+        final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0)
+                && isComponentDirectBootAware;
+        return reportIfDebug(matchesUnaware || matchesAware, flags);
+    }
+
+    private static boolean reportIfDebug(boolean result, long flags) {
+        if (DEBUG && !result) {
+            Slog.i(TAG, "No match!; flags: "
+                    + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+                    + Debug.getCaller());
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
similarity index 67%
rename from services/core/java/com/android/server/pm/parsing/PackageParser2.java
rename to core/java/com/android/internal/pm/parsing/PackageParser2.java
index b6a08a5..2c54672 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing;
+package com.android.internal.pm.parsing;
 
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
-import android.content.Context;
+import android.app.Application;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseInput;
@@ -28,26 +28,21 @@
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.TypedArray;
 import android.os.Build;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.permission.PermissionManager;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 
-import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.PackageManagerService;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 
 /**
  * The v2 of package parsing for use when parsing is initiated in the server and must
@@ -59,50 +54,6 @@
  */
 public class PackageParser2 implements AutoCloseable {
 
-    /**
-     * For parsing inside the system server but outside of {@link PackageManagerService}.
-     * Generally used for parsing information in an APK that hasn't been installed yet.
-     *
-     * This must be called inside the system process as it relies on {@link ServiceManager}.
-     */
-    @NonNull
-    public static PackageParser2 forParsingFileWithDefaults() {
-        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
-                null /* cacheDir */, new Callback() {
-            @Override
-            public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
-                try {
-                    return platformCompat.isChangeEnabled(changeId, appInfo);
-                } catch (Exception e) {
-                    // This shouldn't happen, but assume enforcement if it does
-                    Slog.wtf(TAG, "IPlatformCompat query failed", e);
-                    return true;
-                }
-            }
-
-            @Override
-            public boolean hasFeature(String feature) {
-                // Assume the device doesn't support anything. This will affect permission parsing
-                // and will force <uses-permission/> declarations to include all requiredNotFeature
-                // permissions and exclude all requiredFeature permissions. This mirrors the old
-                // behavior.
-                return false;
-            }
-
-            @Override
-            public Set<String> getHiddenApiWhitelistedApps() {
-                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
-            }
-
-            @Override
-            public Set<String> getInstallConstraintsAllowlist() {
-                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
-            }
-        });
-    }
-
     private static final String TAG = ParsingUtils.TAG;
 
     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
@@ -118,25 +69,34 @@
     private final ThreadLocal<ParseTypeImpl> mSharedResult;
 
     @Nullable
-    protected PackageCacher mCacher;
+    protected IPackageCacher mCacher;
 
-    private final ParsingPackageUtils parsingUtils;
+    private final ParsingPackageUtils mParsingUtils;
 
     public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
-            @Nullable File cacheDir, @NonNull Callback callback) {
+            @Nullable IPackageCacher cacher, @NonNull Callback callback) {
         if (displayMetrics == null) {
             displayMetrics = new DisplayMetrics();
             displayMetrics.setToDefaults();
         }
 
-        PermissionManager permissionManager = ActivityThread.currentApplication()
-                .getSystemService(PermissionManager.class);
-        List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
-                .getSplitPermissions();
+        List<PermissionManager.SplitPermissionInfo> splitPermissions = null;
 
-        mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
+        final Application application = ActivityThread.currentApplication();
+        if (application != null) {
+            final PermissionManager permissionManager =
+                    application.getSystemService(PermissionManager.class);
+            if (permissionManager != null) {
+                splitPermissions = permissionManager.getSplitPermissions();
+            }
+        }
+        if (splitPermissions == null) {
+            splitPermissions = new ArrayList<>();
+        }
 
-        parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
+        mCacher = cacher;
+
+        mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
                 callback);
 
         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
@@ -155,7 +115,7 @@
      */
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
-            throws PackageManagerException {
+            throws PackageParserException {
         var files = packageFile.listFiles();
         // Apk directory is directly nested under the current directory
         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -171,9 +131,9 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+        ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
         if (result.isError()) {
-            throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+            throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
         }
 
@@ -201,12 +161,12 @@
      */
     @AnyThread
     public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
-            throws PackageManagerException {
+            throws PackageParserException {
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+        ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input,
                 packageLite, flags);
         if (result.isError()) {
-            throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+            throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
         }
         return result.getResult().hideAsParsed();
@@ -226,7 +186,7 @@
         mSharedAppInfo.remove();
     }
 
-    public static abstract class Callback implements ParsingPackageUtils.Callback {
+    public abstract static class Callback implements ParsingPackageUtils.Callback {
 
         @Override
         public final ParsingPackage startParsingPackage(@NonNull String packageName,
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d1..56ea52d 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@
     return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
 }
 
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+                                                    jobject sensor, jint index) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+    Vector<Sensor> sensorList;
+    ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+    if (ssize_t(index) >= count) {
+        return false;
+    }
+
+    return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
 static void
 nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
 
@@ -539,6 +552,9 @@
         {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
          (void *)nativeGetSensorAtIndex},
 
+        {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetDefaultDeviceSensorAtIndex},
+
         {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
         {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 052e2f2..d3f3af7 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -610,6 +610,9 @@
     // ringer mode.
     optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
+    // This is an optional feature where ringer mode affects alarm stream as well
+    optional SettingProto mute_alarm_stream_with_ringer_mode = 160 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     reserved 147; // Used to be apply_ramping_ringer
 
     message MultiSim {
@@ -1086,5 +1089,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 160;
+    // Next tag = 161;
 }
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
index e6c6d59..118dfc8 100644
--- a/core/proto/android/view/insetssource.proto
+++ b/core/proto/android/view/insetssource.proto
@@ -26,7 +26,7 @@
  * Represents a {@link android.view.InsetsSource} object.
  */
 message InsetsSourceProto {
-    optional string type = 1;
+    optional string type = 1 [deprecated=true];
     optional .android.graphics.RectProto frame = 2;
     optional .android.graphics.RectProto visible_frame = 3;
     optional bool visible = 4;
diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto
index a01ad8e..882163f 100644
--- a/core/proto/android/view/insetssourceconsumer.proto
+++ b/core/proto/android/view/insetssourceconsumer.proto
@@ -27,11 +27,12 @@
  * Represents a {@link android.view.InsetsSourceConsumer} object.
  */
 message InsetsSourceConsumerProto {
-    optional string internal_insets_type = 1;
+    optional string internal_insets_type = 1 [deprecated=true];
     optional bool has_window_focus = 2;
     optional bool is_requested_visible = 3;
     optional InsetsSourceControlProto source_control = 4;
     optional .android.graphics.RectProto pending_frame = 5;
     optional .android.graphics.RectProto pending_visible_frame = 6;
     optional int32 animation_state = 7;
+    optional int32 type_number = 8;
 }
\ No newline at end of file
diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto
index 3ac3cbf..afab57f 100644
--- a/core/proto/android/view/insetssourcecontrol.proto
+++ b/core/proto/android/view/insetssourcecontrol.proto
@@ -27,7 +27,8 @@
  * Represents a {@link android.view.InsetsSourceControl} object.
  */
 message InsetsSourceControlProto {
-    optional string type = 1;
+    optional string type = 1 [deprecated=true];
     optional .android.graphics.PointProto position = 2;
     optional SurfaceControlProto leash = 3;
+    optional int32 type_number = 4;
 }
\ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 596cfe5..d1143c4 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2986,7 +2986,12 @@
              depends on the number of isolated services that an application starts,
              and how much memory those services save by preloading and sharing memory with
              the app zygote. Therefore, it is recommended to measure memory usage under
-             typical workloads to determine whether it makes sense to use this flag. -->
+             typical workloads to determine whether it makes sense to use this flag.
+
+             <p>There is a limit to the number of isolated services that can be spawned from
+                the Application Zygote; the absolute limit is 100, but due to potential
+                delays in service process cleanup, a much safer limit to use in practice is 50.
+             -->
         <attr name="useAppZygote" format="boolean" />
         <!-- If this is a foreground service, specify its category. -->
         <attr name="foregroundServiceType" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d0de5f0..804e9ef 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2262,6 +2262,9 @@
     <!-- The default min volume for the alarm stream -->
     <integer name="config_audio_alarm_min_vol">1</integer>
 
+    <!-- Flag indicating if ringer mode affects alarm stream -->
+    <bool name="config_audio_ringer_mode_affects_alarm_stream">false</bool>
+
     <!-- The default value for whether head tracking for
          spatial audio is enabled for a newly connected audio device -->
     <bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -6862,4 +6865,8 @@
 
     <!-- Whether the media player is shown on the quick settings -->
     <bool name="config_quickSettingsShowMediaPlayer">true</bool>
+
+    <!-- Defines suitability of the built-in speaker route.
+         Refer to {@link MediaRoute2Info} to see supported values.  -->
+    <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3894330..9589fb0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -288,6 +288,7 @@
   <java-symbol type="integer" name="config_audio_ring_vol_default" />
   <java-symbol type="integer" name="config_audio_ring_vol_steps" />
   <java-symbol type="integer" name="config_audio_alarm_min_vol" />
+  <java-symbol type="bool" name="config_audio_ringer_mode_affects_alarm_stream" />
   <java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
   <java-symbol type="bool" name="config_avoidGfxAccel" />
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
@@ -5306,4 +5307,7 @@
   <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
 
   <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
+
+  <!-- Android MediaRouter framework configs. -->
+  <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 930b1a4..95d5049 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -65,7 +65,7 @@
         mHandler = getInstrumentation().getContext().getMainThreadHandler();
         mController = spy(ClientTransactionListenerController.createInstanceForTesting(
                 mDisplayManager));
-        doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled();
+        doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8308e7c..1617eda 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -20,12 +20,15 @@
 import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.SparseArray
 import androidx.core.util.forEach
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
 import org.junit.Rule
 import kotlin.math.ceil
 import kotlin.math.floor
@@ -45,6 +48,19 @@
     @get:Rule
     val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
+    private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+
+    @Before
+    fun setup() {
+        defaultLookupTables = FontScaleConverterFactory.sLookupTables.clone()
+    }
+
+    @After
+    fun teardown() {
+        // Restore the default tables (since some tests will have added extras to the cache)
+        FontScaleConverterFactory.sLookupTables = defaultLookupTables
+    }
+
     @Test
     fun scale200IsTwiceAtSmallSizes() {
         val table = FontScaleConverterFactory.forScale(2F)!!
@@ -245,7 +261,7 @@
     }
 
     companion object {
-        private const val CONVERSION_TOLERANCE = 0.05f
+        private const val CONVERSION_TOLERANCE = 0.18f
     }
 }
 
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a5bbeb5..9292f66 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -16,7 +16,6 @@
 
 package android.window.flags;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
 import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
 
 import android.platform.test.annotations.Presubmit;
@@ -39,12 +38,6 @@
 public class WindowFlagsTest {
 
     @Test
-    public void testSyncWindowConfigUpdateFlag() {
-        // No crash when accessing the flag.
-        syncWindowConfigUpdateFlag();
-    }
-
-    @Test
     public void testTaskFragmentSystemOrganizerFlag() {
         // No crash when accessing the flag.
         taskFragmentSystemOrganizerFlag();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ff..b209c7c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@
     public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
             new ActivityTestRule<>(ResolverWrapperActivity.class, false,
                     false);
-
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Before
     public void cleanOverrideData() {
         sOverrides.reset();
@@ -1156,6 +1159,97 @@
                 sOverrides.cloneProfileUserHandle)));
     }
 
+    @Test
+    public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+        onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    activity.getPersonalProfileUserHandle());
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromWorkProfile(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+                    activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+                    0).userHandle.equals(activity.getWorkProfileUserHandle()));
+        }
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@
         ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
     }
 
+    private void markPrivateProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+    }
+
     private void setupResolverControllers(
             List<ResolvedComponentInfo> personalResolvedComponentInfos,
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@
                 eq(UserHandle.SYSTEM)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
     }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> resolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(resolvedComponentInfos));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0..862cbd5 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@
         return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
     }
 
+    int getMultiProfilePagerAdapterCount(){
+        return mMultiProfilePagerAdapter.getCount();
+    }
+
     @Override
     public boolean isVoiceInteraction() {
         if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@
     }
 
     @Override
+    protected UserHandle getPrivateProfileUserHandle() {
+        return sOverrides.privateProfileUserHandle;
+    }
+
+    @Override
     protected UserHandle getTabOwnerUserHandleForLaunch() {
         if (sOverrides.tabOwnerUserHandleForLaunch == null) {
             return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
         public UserHandle cloneProfileUserHandle;
+        public UserHandle privateProfileUserHandle;
         public UserHandle tabOwnerUserHandleForLaunch;
         public Integer myUserId;
         public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
             cloneProfileUserHandle = null;
+            privateProfileUserHandle = null;
             tabOwnerUserHandleForLaunch = null;
             myUserId = null;
             hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 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.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d939..0000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187e..0000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 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.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 0000000..bb7d63e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_NonPlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    manifest: "static/AndroidManifest.xml",
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.signature_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde9..0000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 0000000..ee0c0e5
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_UpdateOverlay",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    static_libs: ["androidx.test.rules"],
+    aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["framework/v1/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["framework/v2/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["app/v1/res"],
+    manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["app/v2/res"],
+    manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887..0000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f6..13d38d2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
     <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
     <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
 
-    <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
-        otherwise, it may not be able to enforce provision for managed devices. -->
+    <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+         when app in background; otherwise, it may not be able to enforce provision for managed
+         devices. -->
     <allow-in-power-save package="com.android.devicelockcontroller" />
+    <allow-in-data-usage-save package="com.android.devicelockcontroller" />
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f..b9efe65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@
         <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
         <permission name="android.permission.BIND_WALLPAPER"/>
         <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+        <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.dynsystem">
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index da87339..afd554b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -360,9 +360,7 @@
         if (activities == null) {
             return null;
         }
-        // Already checked nullity in collectNonFinishingActivities.
-        final Rect bounds = getInfo().getConfiguration().windowConfiguration.getBounds();
-        return new ActivityStack(activities, isEmpty(), mToken, bounds, mOverlayTag);
+        return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag);
     }
 
     /** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4511f3b..901d5fa 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@
     description: "Enables left/right split in portrait"
     bug: "291018646"
 }
+
+flag {
+    name: "enable_new_bubble_animations"
+    namespace: "multitasking"
+    description: "Enables new animations for expand and collapse for bubbles"
+    bug: "311450609"
+}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1..e4f793c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
         android:orientation="horizontal"
         android:clickable="true"
         android:focusable="true"
-        android:paddingStart="16dp">
-
+        android:paddingStart="6dp"
+        android:paddingEnd="8dp">
         <ImageView
             android:id="@+id/application_icon"
             android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
             android:id="@+id/application_name"
             android:layout_width="0dp"
             android:layout_height="20dp"
-            android:minWidth="80dp"
+            android:maxWidth="86dp"
             android:textAppearance="@android:style/TextAppearance.Material.Title"
             android:textSize="14sp"
             android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de61..0a40cea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
     <!-- Height of desktop mode caption for fullscreen tasks. -->
     <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
 
+    <!-- Required empty space to be visible for partially offscreen tasks. -->
+    <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+    <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+    <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+    <!-- 32dp width back button + 10dp margin -->
+    <dimen name="caption_left_buttons_width">32dp</dimen>
+
+    <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+    <dimen name="caption_right_buttons_width">126dp</dimen>
+
+    <!-- 2 buttons * 48dp button size. -->
+    <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+    <!-- 22dp padding + 24dp app icon + 16dp expand button.
+         Text varies in size, we will calculate that width separately. -->
+    <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
     <!-- The width of the maximize menu in desktop mode. -->
     <dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 662a5c4..a76bd26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -95,7 +95,6 @@
     private int mMinimumFlyoutWidthLargeScreen;
 
     private PointF mRestingStackPosition;
-    private int[] mPaddings = new int[4];
 
     private boolean mShowingInBubbleBar;
     private final Point mBubbleBarPosition = new Point();
@@ -344,46 +343,44 @@
         final int pointerTotalHeight = getPointerSize();
         final int expandedViewLargeScreenInsetFurthestEdge =
                 getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
+        int[] paddings = new int[4];
         if (mDeviceConfig.isLargeScreen()) {
             // Note:
             // If we're in portrait OR if we're a small tablet, then the two insets values will
             // be equal. If we're landscape and a large tablet, the two values will be different.
             // [left, top, right, bottom]
-            mPaddings[0] = onLeft
+            paddings[0] = onLeft
                     ? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
                     : expandedViewLargeScreenInsetFurthestEdge;
-            mPaddings[1] = 0;
-            mPaddings[2] = onLeft
+            paddings[1] = 0;
+            paddings[2] = onLeft
                     ? expandedViewLargeScreenInsetFurthestEdge
                     : mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
             // Overflow doesn't show manage button / get padding from it so add padding here
-            mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
-            return mPaddings;
+            paddings[3] = isOverflow ? mExpandedViewPadding : 0;
+            return paddings;
         } else {
             int leftPadding = mInsets.left + mExpandedViewPadding;
             int rightPadding = mInsets.right + mExpandedViewPadding;
-            final float expandedViewWidth = isOverflow
-                    ? mOverflowWidth
-                    : mExpandedViewLargeScreenWidth;
             if (showBubblesVertically()) {
                 if (!onLeft) {
                     rightPadding += mBubbleSize - pointerTotalHeight;
                     leftPadding += isOverflow
-                            ? (mPositionRect.width() - rightPadding - expandedViewWidth)
+                            ? (mPositionRect.width() - rightPadding - mOverflowWidth)
                             : 0;
                 } else {
                     leftPadding += mBubbleSize - pointerTotalHeight;
                     rightPadding += isOverflow
-                            ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+                            ? (mPositionRect.width() - leftPadding - mOverflowWidth)
                             : 0;
                 }
             }
             // [left, top, right, bottom]
-            mPaddings[0] = leftPadding;
-            mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
-            mPaddings[2] = rightPadding;
-            mPaddings[3] = 0;
-            return mPaddings;
+            paddings[0] = leftPadding;
+            paddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+            paddings[2] = rightPadding;
+            paddings[3] = 0;
+            return paddings;
         }
     }
 
@@ -395,7 +392,7 @@
     }
 
     /** Gets the y position of the expanded view if it was top-aligned. */
-    public float getExpandedViewYTopAligned() {
+    public int getExpandedViewYTopAligned() {
         final int top = getAvailableRect().top;
         if (showBubblesVertically()) {
             return top - mPointerWidth + mExpandedViewPadding;
@@ -413,7 +410,7 @@
             return getExpandedViewHeightForLargeScreen();
         }
         // Subtract top insets because availableRect.height would account for that
-        int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+        int expandedContainerY = getExpandedViewYTopAligned() - getInsets().top;
         int paddingTop = showBubblesVertically()
                 ? 0
                 : mPointerHeight;
@@ -474,11 +471,11 @@
     public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
         boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
         float expandedViewHeight = getExpandedViewHeight(bubble);
-        float topAlignment = getExpandedViewYTopAligned();
+        int topAlignment = getExpandedViewYTopAligned();
         int manageButtonHeight =
                 isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
 
-        // On largescreen portrait bubbles are bottom aligned.
+        // On large screen portrait bubbles are bottom aligned.
         if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
             return mPositionRect.bottom - manageButtonHeight
                     - getExpandedViewHeightForLargeScreen() - mPointerWidth;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 02af2d0..7798aa7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -420,7 +420,7 @@
             bubbleView.setTranslationY(y);
         }
 
-        final float expandedY = mPositioner.getExpandedViewYTopAligned();
+        final int expandedY = mPositioner.getExpandedViewYTopAligned();
         final boolean draggedOutEnough =
                 y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
         if (draggedOutEnough != mBubbleDraggedOutEnough) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba..af31f5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@
 
 /**
  * The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
  */
 public class HomeTransitionObserver implements TransitionObserver,
         RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@
         for (TransitionInfo.Change change : info.getChanges()) {
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
             if (taskInfo == null
+                    || taskInfo.displayId != DEFAULT_DISPLAY
                     || taskInfo.taskId == -1
                     || !taskInfo.isRunning) {
                 continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c6..72fba3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@
 import android.window.TransitionFilter;
 
 /**
- *  Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
  */
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
 
     /**
-     * Called when a transition changes the visibility of the home activity.
+     * Called when a transition changes the visibility of the home activity on the default display.
      */
     void onHomeVisibilityChanged(in boolean isVisible);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index af69b52..b0d8b47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -757,6 +757,10 @@
             }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
+            } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+                // Remove the change because it should be invisible in the animation.
+                info.getChanges().remove(i);
+                continue;
             }
             // The change has already animated by back gesture, don't need to play transition
             // animation on it.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b..6e7d11d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
@@ -34,6 +35,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 /**
@@ -84,6 +86,69 @@
         mDragPositioningCallback = dragPositioningCallback;
     }
 
+    @Override
+    Rect calculateValidDragArea() {
+        final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.caption_left_buttons_width);
+
+        // On a smaller screen, don't require as much empty space on screen, as offscreen
+        // drags will be restricted too much.
+        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+                .getResources().getConfiguration().smallestScreenWidthDp >= 600
+                ? R.dimen.freeform_required_visible_empty_space_in_header :
+                R.dimen.small_screen_required_visible_empty_space_in_header;
+        final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+                requiredEmptySpaceId);
+
+        final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.caption_right_buttons_width);
+        final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        final int displayWidth = layout.width();
+        final Rect stableBounds = new Rect();
+        layout.getStableBounds(stableBounds);
+        return new Rect(
+                determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth),
+                stableBounds.top,
+                determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+                        displayWidth),
+                determineMaxY(requiredEmptySpace, stableBounds));
+    }
+
+
+    /**
+     * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth) {
+        // Do not let apps with < 48dp empty header space go off the left edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return 0;
+        }
+        return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+    }
+
+    /**
+     * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth, int displayWidth) {
+        // Do not let apps with < 48dp empty header space go off the right edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return displayWidth - taskWidth;
+        }
+        return displayWidth - requiredEmptySpace - leftButtonsWidth;
+    }
+
+    /**
+     * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+        return stableBounds.bottom - requiredEmptySpace;
+    }
+
+
     void setDragDetector(DragDetector dragDetector) {
         mDragDetector = dragDetector;
         mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0..3b6be8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -443,6 +443,66 @@
     }
 
     /**
+     * Determine valid drag area for this task based on elements in the app chip.
+     */
+    @Override
+    Rect calculateValidDragArea() {
+        final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+                mWindowDecorViewHolder).getAppNameTextWidth();
+        final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+        final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.freeform_required_visible_empty_space_in_header);
+        final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.desktop_mode_right_edge_buttons_width);
+        final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        final int displayWidth = layout.width();
+        final Rect stableBounds = new Rect();
+        layout.getStableBounds(stableBounds);
+        return new Rect(
+                determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth),
+                stableBounds.top,
+                determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth, displayWidth),
+                determineMaxY(requiredEmptySpace, stableBounds));
+    }
+
+
+    /**
+     * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth) {
+        // Do not let apps with < 48dp empty header space go off the left edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return 0;
+        }
+        return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+    }
+
+    /**
+     * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth, int displayWidth) {
+        // Do not let apps with < 48dp empty header space go off the right edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return displayWidth - taskWidth;
+        }
+        return displayWidth - requiredEmptySpace - leftButtonsWidth;
+    }
+
+    /**
+     * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+        return stableBounds.bottom - requiredEmptySpace;
+    }
+
+
+    /**
      * Create and display maximize menu window
      */
     void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c7..677c7f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@
 
     /**
      * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
-     * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
-     * stable bounds.
+     * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+     * valid drag area.
      */
-    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
-            PointF repositionStartPoint, float x, float y)  {
+    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+            PointF repositionStartPoint, float x, float y, Rect validDragArea) {
         updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
                 x, y);
+        snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+    }
 
-        // If task is outside of stable bounds (in the status bar area), shift the task down.
-        if (stableBounds.top > repositionTaskBounds.top) {
-            final int yShift =  stableBounds.top - repositionTaskBounds.top;
-            repositionTaskBounds.offset(0, yShift);
+    private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+        // If we were never supplied a valid drag area, do not restrict movement.
+        // Otherwise, we restrict deltas to keep task position inside the Rect.
+        if (validDragArea.width() == 0) return;
+        if (repositionTaskBounds.left < validDragArea.left) {
+            repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+        } else if (repositionTaskBounds.left > validDragArea.right) {
+            repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+        }
+        if (repositionTaskBounds.top < validDragArea.top) {
+            repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+        } else if (repositionTaskBounds.top > validDragArea.bottom) {
+            repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e..5d006fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@
                 y)) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+                    mWindowDecoration.calculateValidDragArea());
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0c..4363558 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@
                 mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
                 y)) {
             DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+                    mDesktopWindowDecoration.calculateValidDragArea());
             DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
                     mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b755..ee0e31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -178,6 +179,13 @@
      */
     abstract void relayout(RunningTaskInfo taskInfo);
 
+    /**
+     * Used by the {@link DragPositioningCallback} associated with the implementing class to
+     * enforce drags ending in a valid position. A null result means no restriction.
+     */
+    @Nullable
+    abstract Rect calculateValidDragArea();
+
     void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
             RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a813..144373f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@
     private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
     private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
     private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+    val appNameTextWidth: Int
+        get() = appNameTextView.width
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04a..e60be71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@
     }
 
     @Test
+    fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100)
+
+        DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+            validDragArea)
+        assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+        assertThat(repositionTaskBounds.right)
+            .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+        assertThat(repositionTaskBounds.bottom)
+            .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+    }
+
+    @Test
     fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
         var hasMoved = false
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2..2ce49cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
         }
+        `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
 
@@ -660,6 +661,38 @@
     }
 
     @Test
+    fun testDragResize_drag_taskPositionedInValidDragArea() {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = VALID_DRAG_AREA.left - 500f
+        val newY = VALID_DRAG_AREA.bottom + 500f
+        taskPositioner.onDragPositioningMove(
+            newX,
+            newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+            newX,
+            newY
+        )
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        VALID_DRAG_AREA.bottom &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        VALID_DRAG_AREA.left
+            }
+        })
+    }
+
+    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@
             DISPLAY_BOUNDS.bottom,
             DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
         )
+        private val VALID_DRAG_AREA = Rect(
+            DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS_LANDSCAPE.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf1..a759b53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
         }
+        `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
 
@@ -379,6 +380,38 @@
     }
 
     @Test
+    fun testDragResize_drag_taskPositionedInValidDragArea() {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = VALID_DRAG_AREA.left - 500f
+        val newY = VALID_DRAG_AREA.bottom + 500f
+        taskPositioner.onDragPositioningMove(
+            newX,
+            newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+            newX,
+            newY
+        )
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        VALID_DRAG_AREA.bottom &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        VALID_DRAG_AREA.left
+            }
+        })
+    }
+
+    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@
             DISPLAY_BOUNDS.bottom,
             DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
         )
+        private val VALID_DRAG_AREA = Rect(
+            DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS_LANDSCAPE.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74..fe508e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -702,6 +702,11 @@
             relayout(taskInfo, false /* applyStartTransactionOnDraw */);
         }
 
+        @Override
+        Rect calculateValidDragArea() {
+            return null;
+        }
+
         void relayout(ActivityManager.RunningTaskInfo taskInfo,
                 boolean applyStartTransactionOnDraw) {
             mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000..67f4775 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.location.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -37,6 +40,23 @@
  * @hide
  */
 public final class LocationResult implements Parcelable {
+    private static final String TAG = "LocationResult";
+
+    // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+    // could likely be lower, but we only want to throw out really absurd values.
+    private static final float MAX_ACCURACY_M = 1000000;
+
+    // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+    // current fastest private jet). Higher speed than the value is considered as a malfunction
+    // than a correct reading.
+    private static final float MAX_SPEED_MPS = 343;
+
+    /** Exception representing an invalid location within a {@link LocationResult}. */
+    public static class BadLocationException extends Exception {
+        public BadLocationException(String message) {
+            super(message);
+        }
+    }
 
     /**
      * Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@
      *
      * @hide
      */
-    public @NonNull LocationResult validate() {
+    public @NonNull LocationResult validate() throws BadLocationException {
         long prevElapsedRealtimeNs = 0;
         final int size = mLocations.size();
         for (int i = 0; i < size; ++i) {
             Location location = mLocations.get(i);
-            if (!location.isComplete()) {
-                throw new IllegalArgumentException(
-                        "incomplete location at index " + i + ": " + mLocations);
-            }
-            if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
-                throw new IllegalArgumentException(
-                        "incorrectly ordered location at index " + i + ": " + mLocations);
+            if (Flags.locationValidation()) {
+                if (location.getLatitude() < -90.0
+                        || location.getLatitude() > 90.0
+                        || location.getLongitude() < -180.0
+                        || location.getLongitude() > 180.0
+                        || Double.isNaN(location.getLatitude())
+                        || Double.isNaN(location.getLongitude())) {
+                    throw new BadLocationException("location must have valid lat/lng");
+                }
+                if (!location.hasAccuracy()) {
+                    throw new BadLocationException("location must have accuracy");
+                }
+                if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+                    throw new BadLocationException("location must have reasonable accuracy");
+                }
+                if (location.getTime() < 0) {
+                    throw new BadLocationException("location must have valid time");
+                }
+                if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+                    throw new BadLocationException(
+                            "location must have valid monotonically increasing realtime");
+                }
+                if (location.getElapsedRealtimeNanos()
+                        > SystemClock.elapsedRealtimeNanos()) {
+                    throw new BadLocationException("location must not have realtime in the future");
+                }
+                if (!location.isMock()) {
+                    if (location.getProvider() == null) {
+                        throw new BadLocationException("location must have valid provider");
+                    }
+                    if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                        throw new BadLocationException("location must not be at 0,0");
+                    }
+                }
+
+                if (location.hasSpeed() && (location.getSpeed() < 0
+                        || location.getSpeed() > MAX_SPEED_MPS)) {
+                    Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+                    location.removeSpeed();
+                }
+            } else {
+                if (!location.isComplete()) {
+                    throw new IllegalArgumentException(
+                            "incomplete location at index " + i + ": " + mLocations);
+                }
+                if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+                    throw new IllegalArgumentException(
+                            "incorrectly ordered location at index " + i + ": " + mLocations);
+                }
             }
             prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
         }
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056..a8464d3 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@
     description: "Flag for releasing SUPL connection on timeout"
     bug: "315024652"
 }
+
+flag {
+    name: "location_validation"
+    namespace: "location"
+    description: "Flag for location validation"
+    bug: "314328533"
+}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index 29bfd1a..e2dddad 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -19,6 +19,7 @@
 import android.media.MediaRoute2Info;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 /**
  * @hide
@@ -35,5 +36,6 @@
      * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
      */
     void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
-        in MediaRoute2Info route);
+        in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle,
+        in String transferInitiatorPackageName);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index fa4d1a1..04e99ea 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -64,7 +64,8 @@
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
             in RoutingSessionInfo oldSession, in MediaRoute2Info route,
-            in @nullable Bundle sessionHints);
+            in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle,
+            in String transferInitiatorPackageName);
     void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -84,13 +85,16 @@
     void stopScan(IMediaRouter2Manager manager);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
-            in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
+            in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
+            in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName);
     void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, in MediaRoute2Info route);
     void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, in MediaRoute2Info route);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)")
     void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
-            String sessionId, in MediaRoute2Info route);
+            String sessionId, in MediaRoute2Info route,
+            in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName);
     void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, int volume);
     void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 8ad3587..0eabe66 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -19,6 +19,7 @@
 import static android.media.MediaRouter2Utils.toUniqueId;
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
 
 import android.annotation.FlaggedApi;
@@ -479,6 +480,37 @@
     public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
             "android.media.route.feature.REMOTE_GROUP_PLAYBACK";
 
+    /** Indicates the route is always suitable for media playback. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0;
+
+    /**
+     * Indicates that the route is suitable for media playback only after explicit user selection.
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1;
+
+    /** Indicates that the route is never suitable for media playback. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2;
+
+    /**
+     * Route suitability status.
+     *
+     * <p>Signals whether the route is suitable to play media.
+     *
+     * @hide
+     */
+    @IntDef(
+            value = {
+                SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER,
+                SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER,
+                SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public @interface SuitabilityStatus {}
+
     private final String mId;
     private final CharSequence mName;
     private final List<String> mFeatures;
@@ -500,6 +532,7 @@
     private final String mProviderId;
     private final boolean mIsVisibilityRestricted;
     private final Set<String> mAllowedPackages;
+    @SuitabilityStatus private final int mSuitabilityStatus;
 
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
@@ -521,6 +554,7 @@
         mProviderId = builder.mProviderId;
         mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
         mAllowedPackages = builder.mAllowedPackages;
+        mSuitabilityStatus = builder.mSuitabilityStatus;
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -544,6 +578,7 @@
         mProviderId = in.readString();
         mIsVisibilityRestricted = in.readBoolean();
         mAllowedPackages = Set.of(in.createString8Array());
+        mSuitabilityStatus = in.readInt();
     }
 
     /**
@@ -778,6 +813,13 @@
                 || mAllowedPackages.contains(packageName);
     }
 
+    /** Returns the route suitability status. */
+    @SuitabilityStatus
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public int getSuitabilityStatus() {
+        return mSuitabilityStatus;
+    }
+
     /**
      * Dumps the current state of the object to the given {@code pw} as a human-readable string.
      *
@@ -809,6 +851,7 @@
         pw.println(indent + "mProviderId=" + mProviderId);
         pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
         pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
+        pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
     }
 
     private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -861,39 +904,74 @@
                 && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
                 && Objects.equals(mProviderId, other.mProviderId)
                 && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
-                && Objects.equals(mAllowedPackages, other.mAllowedPackages);
+                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
+                && mSuitabilityStatus == other.mSuitabilityStatus;
     }
 
     @Override
     public int hashCode() {
         // Note: mExtras is not included.
-        return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
-                mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
-                mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
-                mAllowedPackages);
+        return Objects.hash(
+                mId,
+                mName,
+                mFeatures,
+                mType,
+                mIsSystem,
+                mIconUri,
+                mDescription,
+                mConnectionState,
+                mClientPackageName,
+                mPackageName,
+                mVolumeHandling,
+                mVolumeMax,
+                mVolume,
+                mAddress,
+                mDeduplicationIds,
+                mProviderId,
+                mIsVisibilityRestricted,
+                mAllowedPackages,
+                mSuitabilityStatus);
     }
 
     @Override
     public String toString() {
         // Note: mExtras is not printed here.
-        StringBuilder result = new StringBuilder()
-                .append("MediaRoute2Info{ ")
-                .append("id=").append(getId())
-                .append(", name=").append(getName())
-                .append(", features=").append(getFeatures())
-                .append(", iconUri=").append(getIconUri())
-                .append(", description=").append(getDescription())
-                .append(", connectionState=").append(getConnectionState())
-                .append(", clientPackageName=").append(getClientPackageName())
-                .append(", volumeHandling=").append(getVolumeHandling())
-                .append(", volumeMax=").append(getVolumeMax())
-                .append(", volume=").append(getVolume())
-                .append(", address=").append(getAddress())
-                .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
-                .append(", providerId=").append(getProviderId())
-                .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
-                .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
-                .append(" }");
+        StringBuilder result =
+                new StringBuilder()
+                        .append("MediaRoute2Info{ ")
+                        .append("id=")
+                        .append(getId())
+                        .append(", name=")
+                        .append(getName())
+                        .append(", features=")
+                        .append(getFeatures())
+                        .append(", iconUri=")
+                        .append(getIconUri())
+                        .append(", description=")
+                        .append(getDescription())
+                        .append(", connectionState=")
+                        .append(getConnectionState())
+                        .append(", clientPackageName=")
+                        .append(getClientPackageName())
+                        .append(", volumeHandling=")
+                        .append(getVolumeHandling())
+                        .append(", volumeMax=")
+                        .append(getVolumeMax())
+                        .append(", volume=")
+                        .append(getVolume())
+                        .append(", address=")
+                        .append(getAddress())
+                        .append(", deduplicationIds=")
+                        .append(String.join(",", getDeduplicationIds()))
+                        .append(", providerId=")
+                        .append(getProviderId())
+                        .append(", isVisibilityRestricted=")
+                        .append(mIsVisibilityRestricted)
+                        .append(", allowedPackages=")
+                        .append(String.join(",", mAllowedPackages))
+                        .append(", suitabilityStatus=")
+                        .append(mSuitabilityStatus)
+                        .append(" }");
         return result.toString();
     }
 
@@ -923,6 +1001,7 @@
         dest.writeString(mProviderId);
         dest.writeBoolean(mIsVisibilityRestricted);
         dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+        dest.writeInt(mSuitabilityStatus);
     }
 
     private static String getDeviceTypeString(@Type int deviceType) {
@@ -1005,6 +1084,7 @@
         private String mProviderId;
         private boolean mIsVisibilityRestricted;
         private Set<String> mAllowedPackages;
+        @SuitabilityStatus private int mSuitabilityStatus;
 
         /**
          * Constructor for builder to create {@link MediaRoute2Info}.
@@ -1028,6 +1108,7 @@
             mFeatures = new ArrayList<>();
             mDeduplicationIds = Set.of();
             mAllowedPackages = Set.of();
+            mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
         }
 
         /**
@@ -1075,6 +1156,7 @@
             mProviderId = routeInfo.mProviderId;
             mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
             mAllowedPackages = routeInfo.mAllowedPackages;
+            mSuitabilityStatus = routeInfo.mSuitabilityStatus;
         }
 
         /**
@@ -1318,6 +1400,23 @@
         }
 
         /**
+         * Sets route suitability status.
+         *
+         * <p>The default value is {@link
+         * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}.
+         *
+         * <p> Apps are not supposed to set {@link
+         * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system
+         * route with such status throws {@link SecurityException}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) {
+            mSuitabilityStatus = suitabilityStatus;
+            return this;
+        }
+
+        /**
          * Builds the {@link MediaRoute2Info media route info}.
          *
          * @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ba26df9..5e23551 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
 import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2;
 
@@ -699,15 +700,48 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
-        mImpl.transfer(controller.getRoutingSessionInfo(), route);
+        mImpl.transfer(
+                controller.getRoutingSessionInfo(),
+                route,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
+    }
+
+    /**
+     * Transfers the media of a routing controller to the given route.
+     *
+     * <p>This will be no-op for non-system media routers.
+     *
+     * @param controller a routing controller controlling media routing.
+     * @param route the route you want to transfer the media to.
+     * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer
+     *     request.
+     * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
+     *     This value is used with the user handle to populate {@link
+     *     RoutingController#wasTransferRequestedBySelf()}.
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public void transfer(
+            @NonNull RoutingController controller,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
+        mImpl.transfer(
+                controller.getRoutingSessionInfo(),
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     void requestCreateController(
             @NonNull RoutingController controller,
             @NonNull MediaRoute2Info route,
-            long managerRequestId) {
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
 
         final int requestId = mNextRequestId.getAndIncrement();
 
@@ -736,7 +770,9 @@
                         managerRequestId,
                         controller.getRoutingSessionInfo(),
                         route,
-                        controllerHints);
+                        controllerHints,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 Log.e(TAG, "createControllerForTransfer: "
                                 + "Failed to request for creating a controller.", ex);
@@ -1053,7 +1089,11 @@
     }
 
     void onRequestCreateControllerByManagerOnHandler(
-            RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) {
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Log.i(
                 TAG,
                 TextUtils.formatSimple(
@@ -1070,7 +1110,8 @@
         if (controller == null) {
             return;
         }
-        requestCreateController(controller, route, managerRequestId);
+        requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     private List<MediaRoute2Info> getSortedRoutes(
@@ -1469,6 +1510,21 @@
         }
 
         /**
+         * Returns whether the transfer was requested by the calling app (as determined by comparing
+         * {@link UserHandle} and package name).
+         */
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public boolean wasTransferRequestedBySelf() {
+            RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
+
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+            return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+                    && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+        }
+
+        /**
          * Returns the current {@link RoutingSessionInfo} associated to this controller.
          */
         @NonNull
@@ -1980,14 +2036,20 @@
 
         @Override
         public void requestCreateSessionByManager(
-                long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+                long managerRequestId,
+                RoutingSessionInfo oldSession,
+                MediaRoute2Info route,
+                UserHandle transferInitiatorUserHandle,
+                String transferInitiatorPackageName) {
             mHandler.sendMessage(
                     obtainMessage(
                             MediaRouter2::onRequestCreateControllerByManagerOnHandler,
                             MediaRouter2.this,
                             oldSession,
                             route,
-                            managerRequestId));
+                            managerRequestId,
+                            transferInitiatorUserHandle,
+                            transferInitiatorPackageName));
         }
     }
 
@@ -2027,7 +2089,11 @@
 
         void stop();
 
-        void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route);
+        void transfer(
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName);
 
         List<RoutingController> getControllers();
 
@@ -2220,7 +2286,11 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(targetSession, route);
+            transfer(
+                    targetSession,
+                    route,
+                    android.os.Process.myUserHandle(),
+                    mContext.getPackageName());
         }
 
         @Override
@@ -2243,14 +2313,24 @@
          *
          * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
          * @param route The {@link MediaRoute2Info route} to transfer to.
-         * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info)
+         * @param transferInitiatorUserHandle The user handle of the app that initiated the
+         *     transfer.
+         * @param transferInitiatorPackageName The package name if of the app that initiated the
+         *     transfer.
+         * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String)
          * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
          */
         @Override
+        @SuppressWarnings("AndroidFrameworkRequiresPermission")
         public void transfer(
-                @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
             Objects.requireNonNull(route, "route must not be null");
+            Objects.requireNonNull(transferInitiatorUserHandle);
+            Objects.requireNonNull(transferInitiatorPackageName);
 
             Log.v(
                     TAG,
@@ -2268,9 +2348,14 @@
             }
 
             if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                transferToRoute(sessionInfo, route);
+                transferToRoute(
+                        sessionInfo,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } else {
-                requestCreateSession(sessionInfo, route);
+                requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         }
 
@@ -2282,21 +2367,30 @@
          * RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes()
          * transferable routes list}. Otherwise, the request will fail.
          *
-         * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request
-         * an out-of-session transfer.
+         * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request an
+         * out-of-session transfer.
          *
          * @param session The {@link RoutingSessionInfo routing session} to transfer.
          * @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link
          *     RoutingSessionInfo routing session's} {@link
          *     RoutingSessionInfo#getTransferableRoutes() transferable routes}.
          */
+        @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
         private void transferToRoute(
-                @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo session,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             int requestId = createTransferRequest(session, route);
 
             try {
                 mMediaRouterService.transferToRouteWithManager(
-                        mClient, requestId, session.getId(), route);
+                        mClient,
+                        requestId,
+                        session.getId(),
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -2317,7 +2411,10 @@
          * @param route The {@link MediaRoute2Info route} to transfer to.
          */
         private void requestCreateSession(
-                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo oldSession,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
                 Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
                 this.onTransferFailed(oldSession, route);
@@ -2328,7 +2425,12 @@
 
             try {
                 mMediaRouterService.requestCreateSessionWithManager(
-                        mClient, requestId, oldSession, route);
+                        mClient,
+                        requestId,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -3055,7 +3157,8 @@
                 return;
             }
 
-            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
+                    android.os.Process.myUserHandle(), mContext.getPackageName());
         }
 
         @Override
@@ -3071,7 +3174,11 @@
          * #transferTo(MediaRoute2Info)}.
          */
         @Override
-        public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
+        public void transfer(
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             // Do nothing.
         }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 830708c..06c0996 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -18,9 +18,11 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -28,6 +30,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -467,30 +470,42 @@
      * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
      * session based on the provided package name.
      */
-    public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void transfer(
+            @NonNull String packageName,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
         List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
         RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-        transfer(targetSession, route);
+        transfer(targetSession, route, userHandle, packageName);
     }
 
     /**
      * Transfers a routing session to a media route.
+     *
      * <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called
      * depending on the result.
      *
      * @param sessionInfo the routing session info to transfer
      * @param route the route transfer to
-     *
+     * @param transferInitiatorUserHandle the user handle of an app initiated the transfer
+     * @param transferInitiatorPackageName the package name of an app initiated the transfer
      * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
      * @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info)
      */
-    public void transfer(@NonNull RoutingSessionInfo sessionInfo,
-            @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void transfer(
+            @NonNull RoutingSessionInfo sessionInfo,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
+        Objects.requireNonNull(transferInitiatorPackageName);
 
         Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route);
 
@@ -503,9 +518,11 @@
         }
 
         if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-            transferToRoute(sessionInfo, route);
+            transferToRoute(
+                    sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName);
         } else {
-            requestCreateSession(sessionInfo, route);
+            requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         }
     }
 
@@ -873,19 +890,30 @@
      *
      * @hide
      */
-    private void transferToRoute(@NonNull RoutingSessionInfo session,
-            @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    private void transferToRoute(
+            @NonNull RoutingSessionInfo session,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         int requestId = createTransferRequest(session, route);
 
         try {
             mMediaRouterService.transferToRouteWithManager(
-                    mClient, requestId, session.getId(), route);
+                    mClient,
+                    requestId,
+                    session.getId(),
+                    route,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
     }
 
-    private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) {
+    private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiationPackageName) {
         if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
             Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
             notifyTransferFailed(oldSession, route);
@@ -896,7 +924,8 @@
 
         try {
             mMediaRouterService.requestCreateSessionWithManager(
-                    mClient, requestId, oldSession, route);
+                    mClient, requestId, oldSession, route, transferInitiatorUserHandle,
+                    transferInitiationPackageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index a77c943..d28c26d 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -16,18 +16,25 @@
 
 package android.media;
 
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -55,6 +62,33 @@
     private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
     private static final String KEY_VOLUME_HANDLING = "volumeHandling";
 
+    /**
+     * Indicates that the transfer happened by the default logic without explicit system's or user's
+     * request.
+     *
+     * <p>For example, an automatically connected Bluetooth device will have this transfer reason.
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_FALLBACK = 0;
+
+    /** Indicates that the transfer happened from within a privileged application. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1;
+
+    /** Indicates that the transfer happened from a non-privileged app. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_APP = 2;
+
+    /**
+     * Indicates the transfer reason.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransferReason {}
+
     @NonNull
     final String mId;
     @Nullable
@@ -82,6 +116,10 @@
     final Bundle mControlHints;
     final boolean mIsSystemSession;
 
+    @TransferReason final int mTransferReason;
+
+    @Nullable final UserHandle mTransferInitiatorUserHandle;
+    @Nullable final String mTransferInitiatorPackageName;
 
     RoutingSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
@@ -116,6 +154,9 @@
                         volumeAdjustmentForRemoteGroupSessions);
 
         mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
+        mTransferReason = builder.mTransferReason;
+        mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle;
+        mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName;
     }
 
     RoutingSessionInfo(@NonNull Parcel src) {
@@ -140,6 +181,9 @@
 
         mControlHints = src.readBundle();
         mIsSystemSession = src.readBoolean();
+        mTransferReason = src.readInt();
+        mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+        mTransferInitiatorPackageName = src.readString();
     }
 
     @Nullable
@@ -330,6 +374,27 @@
         return mIsSystemSession;
     }
 
+    /** Returns the transfer reason for this routing session. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @TransferReason
+    public int getTransferReason() {
+        return mTransferReason;
+    }
+
+    /** @hide */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @Nullable
+    public UserHandle getTransferInitiatorUserHandle() {
+        return mTransferInitiatorUserHandle;
+    }
+
+    /** @hide */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @Nullable
+    public String getTransferInitiatorPackageName() {
+        return mTransferInitiatorPackageName;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -351,6 +416,13 @@
         dest.writeInt(mVolume);
         dest.writeBundle(mControlHints);
         dest.writeBoolean(mIsSystemSession);
+        dest.writeInt(mTransferReason);
+        if (mTransferInitiatorUserHandle != null) {
+            mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
+        } else {
+            dest.writeParcelable(null, /* flags= */ 0);
+        }
+        dest.writeString(mTransferInitiatorPackageName);
     }
 
     /**
@@ -379,6 +451,9 @@
         pw.println(indent + "mVolume=" + mVolume);
         pw.println(indent + "mControlHints=" + mControlHints);
         pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
+        pw.println(indent + "mTransferReason=" + mTransferReason);
+        pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle);
+        pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName);
     }
 
     @Override
@@ -406,39 +481,69 @@
                 && Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
-                && (mVolume == other.mVolume);
+                && (mVolume == other.mVolume)
+                && (mTransferReason == other.mTransferReason)
+                && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
+                && Objects.equals(
+                mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId,
-                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
-                mVolumeMax, mVolumeHandling, mVolume);
+        return Objects.hash(
+                mId,
+                mName,
+                mOwnerPackageName,
+                mClientPackageName,
+                mProviderId,
+                mSelectedRoutes,
+                mSelectableRoutes,
+                mDeselectableRoutes,
+                mTransferableRoutes,
+                mVolumeMax,
+                mVolumeHandling,
+                mVolume,
+                mTransferReason,
+                mTransferInitiatorUserHandle,
+                mTransferInitiatorPackageName);
     }
 
     @Override
     public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("RoutingSessionInfo{ ")
-                .append("sessionId=").append(getId())
-                .append(", name=").append(getName())
-                .append(", clientPackageName=").append(getClientPackageName())
-                .append(", selectedRoutes={")
-                .append(String.join(",", getSelectedRoutes()))
-                .append("}")
-                .append(", selectableRoutes={")
-                .append(String.join(",", getSelectableRoutes()))
-                .append("}")
-                .append(", deselectableRoutes={")
-                .append(String.join(",", getDeselectableRoutes()))
-                .append("}")
-                .append(", transferableRoutes={")
-                .append(String.join(",", getTransferableRoutes()))
-                .append("}")
-                .append(", volumeHandling=").append(getVolumeHandling())
-                .append(", volumeMax=").append(getVolumeMax())
-                .append(", volume=").append(getVolume())
-                .append(" }");
+        StringBuilder result =
+                new StringBuilder()
+                        .append("RoutingSessionInfo{ ")
+                        .append("sessionId=")
+                        .append(getId())
+                        .append(", name=")
+                        .append(getName())
+                        .append(", clientPackageName=")
+                        .append(getClientPackageName())
+                        .append(", selectedRoutes={")
+                        .append(String.join(",", getSelectedRoutes()))
+                        .append("}")
+                        .append(", selectableRoutes={")
+                        .append(String.join(",", getSelectableRoutes()))
+                        .append("}")
+                        .append(", deselectableRoutes={")
+                        .append(String.join(",", getDeselectableRoutes()))
+                        .append("}")
+                        .append(", transferableRoutes={")
+                        .append(String.join(",", getTransferableRoutes()))
+                        .append("}")
+                        .append(", volumeHandling=")
+                        .append(getVolumeHandling())
+                        .append(", volumeMax=")
+                        .append(getVolumeMax())
+                        .append(", volume=")
+                        .append(getVolume())
+                        .append(", transferReason=")
+                        .append(getTransferReason())
+                        .append(", transferInitiatorUserHandle=")
+                        .append(getTransferInitiatorUserHandle())
+                        .append(", transferInitiatorPackageName=")
+                        .append(getTransferInitiatorPackageName())
+                        .append(" }");
         return result.toString();
     }
 
@@ -494,6 +599,9 @@
         @Nullable
         private Bundle mControlHints;
         private boolean mIsSystemSession;
+        @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK;
+        @Nullable private UserHandle mTransferInitiatorUserHandle;
+        @Nullable private String mTransferInitiatorPackageName;
 
         /**
          * Constructor for builder to create {@link RoutingSessionInfo}.
@@ -555,6 +663,9 @@
 
             mControlHints = sessionInfo.mControlHints;
             mIsSystemSession = sessionInfo.mIsSystemSession;
+            mTransferReason = sessionInfo.mTransferReason;
+            mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle;
+            mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName;
         }
 
         /**
@@ -784,6 +895,35 @@
         }
 
         /**
+         * Sets transfer reason for the current session.
+         *
+         * <p>By default the transfer reason is set to {@link
+         * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setTransferReason(@TransferReason int transferReason) {
+            mTransferReason = transferReason;
+            return this;
+        }
+
+        /**
+         * Sets the user handle and package name of the process that initiated the transfer.
+         *
+         * <p>By default the transfer initiation user handle and package name are set to {@code
+         * null}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setTransferInitiator(
+                @Nullable UserHandle transferInitiatorUserHandle,
+                @Nullable String transferInitiatorPackageName) {
+            mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+            mTransferInitiatorPackageName = transferInitiatorPackageName;
+            return this;
+        }
+
+        /**
          * Builds a routing session info.
          *
          * @throws IllegalArgumentException if no selected routes are added.
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 07f63e5..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -69,3 +69,17 @@
     description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
     bug: "314324170"
 }
+
+flag {
+     name: "enable_built_in_speaker_route_suitability_statuses"
+     namespace: "media_solutions"
+     description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+     bug: "279555229"
+}
+
+flag {
+    name: "enable_notifying_activity_manager_with_media_session_status_change"
+    namespace: "media_solutions"
+    description: "Notify ActivityManager with the changes in playback state of the media session."
+    bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
         return false;
     }
 
+    /**
+     * Returns whether the service holding the media session should run in the foreground when the
+     * media session has this playback state or not.
+     *
+     * @hide
+     */
+    public boolean shouldAllowServiceToRunInForeground() {
+        switch (mState) {
+            case PlaybackState.STATE_PLAYING:
+            case PlaybackState.STATE_FAST_FORWARDING:
+            case PlaybackState.STATE_REWINDING:
+            case PlaybackState.STATE_BUFFERING:
+            case PlaybackState.STATE_CONNECTING:
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
             new Parcelable.Creator<PlaybackState>() {
         @Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 757e9f8..3fcb871 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@
                                            mpuSequenceNumber, isPesPrivateData, sc,
                                            audioDescriptor.get(), presentationsJObj.get()));
 
+    // Protect mFilterClient from being set to null.
+    android::Mutex::Autolock autoLock(mLock);
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
         (dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@
             }
         }
     }
-    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter.get(), nullptr)) {
-        jmethodID methodID = gFields.onFilterEventID;
-        if (mSharedFilter) {
-            methodID = gFields.onSharedFilterEventID;
+
+    ScopedLocalRef<jobject> filter(env);
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (env->IsSameObject(mFilterObj, nullptr)) {
+            ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+                  "Filter object has been freed. Ignoring callback.");
+            return;
+        } else {
+            filter.reset(env->NewLocalRef(mFilterObj));
         }
-        env->CallVoidMethod(filter.get(), methodID, array.get());
-    } else {
-        ALOGE("FilterClientCallbackImpl::onFilterEvent:"
-              "Filter object has been freed. Ignoring callback.");
     }
+
+    jmethodID methodID = gFields.onFilterEventID;
+    if (mSharedFilter) {
+        methodID = gFields.onSharedFilterEventID;
+    }
+    env->CallVoidMethod(filter.get(), methodID, array.get());
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
     ALOGV("FilterClientCallbackImpl::onFilterStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter.get(), nullptr)) {
-        jmethodID methodID = gFields.onFilterStatusID;
-        if (mSharedFilter) {
-            methodID = gFields.onSharedFilterStatusID;
+    ScopedLocalRef<jobject> filter(env);
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (env->IsSameObject(filter.get(), nullptr)) {
+            ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+                  "Filter object has been freed. Ignoring callback.");
+            return;
+        } else {
+            filter.reset(env->NewLocalRef(mFilterObj));
         }
-        env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
-    } else {
-        ALOGE("FilterClientCallbackImpl::onFilterStatus:"
-              "Filter object has been freed. Ignoring callback.");
     }
+
+    jmethodID methodID = gFields.onFilterStatusID;
+    if (mSharedFilter) {
+        methodID = gFields.onSharedFilterStatusID;
+    }
+    env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
 }
 
 void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
     ALOGV("FilterClientCallbackImpl::setFilter");
     // Java Object
+    android::Mutex::Autolock autoLock(mLock);
     mFilterObj = filterObj;
     mFilterClient = filterClient;
     mSharedFilter = false;
@@ -979,6 +995,7 @@
 void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
     ALOGV("FilterClientCallbackImpl::setFilter");
     // Java Object
+    android::Mutex::Autolock autoLock(mLock);
     mFilterObj = filterObj;
     mFilterClient = filterClient;
     mSharedFilter = true;
@@ -1047,11 +1064,14 @@
 
 FilterClientCallbackImpl::~FilterClientCallbackImpl() {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    if (mFilterObj != nullptr) {
-        env->DeleteWeakGlobalRef(mFilterObj);
-        mFilterObj = nullptr;
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (mFilterObj != nullptr) {
+            env->DeleteWeakGlobalRef(mFilterObj);
+            mFilterObj = nullptr;
+        }
+        mFilterClient = nullptr;
     }
-    mFilterClient = nullptr;
     env->DeleteGlobalRef(mEventClass);
     env->DeleteGlobalRef(mSectionEventClass);
     env->DeleteGlobalRef(mMediaEventClass);
@@ -3696,7 +3716,7 @@
                     "([Landroid/media/tv/tuner/filter/FilterEvent;)V");
 
     jclass sharedFilterClazz = env->FindClass("android/media/tv/tuner/filter/SharedFilter");
-    gFields.sharedFilterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
+    gFields.sharedFilterContext = env->GetFieldID(sharedFilterClazz, "mNativeContext", "J");
     gFields.sharedFilterInitID = env->GetMethodID(sharedFilterClazz, "<init>", "()V");
     gFields.onSharedFilterStatusID = env->GetMethodID(sharedFilterClazz, "onFilterStatus", "(I)V");
     gFields.onSharedFilterEventID =
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998d..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@
 private:
     jweak mFilterObj;
     sp<FilterClient> mFilterClient;
+    android::Mutex mLock;
     jclass mEventClass;
     jclass mSectionEventClass;
     jclass mMediaEventClass;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 8ed4bf2..c836df3 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -385,7 +385,9 @@
         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertThat(routeToSelect).isNotNull();
 
-        mManager.transfer(mPackageName, routeToSelect);
+        mManager.transfer(
+                mPackageName, routeToSelect,
+                android.os.Process.myUserHandle());
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(mManager.getRemoteSessions()).hasSize(1);
     }
@@ -411,7 +413,9 @@
 
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
 
-        mManager.transfer(mPackageName, routeToSelect);
+        mManager.transfer(
+                mPackageName, routeToSelect,
+                android.os.Process.myUserHandle());
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -450,7 +454,11 @@
                 .addFeature(FEATURE_REMOTE_PLAYBACK)
                 .build();
 
-        mManager.transfer(mManager.getSystemRoutingSession(null), unknownRoute);
+        mManager.transfer(
+                mManager.getSystemRoutingSession(null),
+                unknownRoute,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
         assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
         assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
@@ -484,7 +492,11 @@
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
         assertThat(mRouter2.getControllers()).hasSize(1);
 
-        mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect);
+        mManager.transfer(
+                mManager.getRoutingSessions(mPackageName).get(0),
+                routeToSelect,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
         assertThat(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(2);
@@ -516,7 +528,11 @@
             }
         });
         awaitOnRouteChangedManager(
-                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
+                () ->
+                        mManager.transfer(
+                                mPackageName,
+                                routes.get(ROUTE_ID1),
+                                android.os.Process.myUserHandle()),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -527,7 +543,11 @@
         RoutingSessionInfo sessionInfo = sessions.get(1);
 
         awaitOnRouteChangedManager(
-                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+                () ->
+                        mManager.transfer(
+                                mPackageName,
+                                routes.get(ROUTE_ID5_TO_TRANSFER_TO),
+                                android.os.Process.myUserHandle()),
                 ROUTE_ID5_TO_TRANSFER_TO,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
@@ -585,9 +605,11 @@
         assertThat(route1).isNotNull();
         assertThat(route2).isNotNull();
 
-        mManager.transfer(mPackageName, route1);
+        mManager.transfer(
+                mPackageName, route1, android.os.Process.myUserHandle());
         assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
-        mManager.transfer(mPackageName, route2);
+        mManager.transfer(
+                mPackageName, route2, android.os.Process.myUserHandle());
         assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         // onTransferFailed/onSessionReleased should not be called.
@@ -634,7 +656,11 @@
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
         RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1);
-        mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED));
+        mManager.transfer(
+                targetSession,
+                routes.get(ROUTE_ID6_TO_BE_IGNORED),
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
 
         assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
         assertThat(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS,
@@ -705,7 +731,10 @@
             }
         });
 
-        mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
+        mManager.transfer(
+                mPackageName,
+                routes.get(ROUTE_ID1),
+                android.os.Process.myUserHandle());
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -860,7 +889,8 @@
         });
 
         mRouter2.setOnGetControllerHintsListener(listener);
-        mManager.transfer(mPackageName, route);
+        mManager.transfer(
+                mPackageName, route, android.os.Process.myUserHandle());
         assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -905,7 +935,10 @@
             }
         });
 
-        mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+        mManager.transfer(
+                mPackageName,
+                routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT),
+                android.os.Process.myUserHandle());
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
 
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c4c8128..abe4a3d 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,10 +18,12 @@
 
 #include <aidl/android/hardware/power/SessionHint.h>
 #include <aidl/android/hardware/power/SessionMode.h>
+#include <android-base/stringprintf.h>
 #include <android/WorkDuration.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
+#include <android/trace.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
@@ -30,6 +32,7 @@
 #include <utils/SystemClock.h>
 
 #include <chrono>
+#include <set>
 #include <utility>
 #include <vector>
 
@@ -40,6 +43,7 @@
 
 using AidlSessionHint = aidl::android::hardware::power::SessionHint;
 using AidlSessionMode = aidl::android::hardware::power::SessionMode;
+using android::base::StringPrintf;
 
 struct APerformanceHintSession;
 
@@ -98,10 +102,21 @@
     std::vector<int64_t> mLastHintSentTimestamp;
     // Cached samples
     std::vector<WorkDuration> mActualWorkDurations;
+    std::string mSessionName;
+    static int32_t sIDCounter;
+    // The most recent set of thread IDs
+    std::vector<int32_t> mLastThreadIDs;
+    // Tracing helpers
+    void traceThreads(std::vector<int32_t>& tids);
+    void tracePowerEfficient(bool powerEfficient);
+    void traceActualDuration(int64_t actualDuration);
+    void traceBatchSize(size_t batchSize);
+    void traceTargetDuration(int64_t targetDuration);
 };
 
 static IHintManager* gIHintManagerForTesting = nullptr;
 static APerformanceHintManager* gHintManagerForTesting = nullptr;
+int32_t APerformanceHintSession::sIDCounter = 0;
 
 // ===================================== APerformanceHintManager implementation
 APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
@@ -150,8 +165,12 @@
     if (!ret.isOk() || !session) {
         return nullptr;
     }
-    return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
-                                       initialTargetWorkDurationNanos);
+    auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
+                                           initialTargetWorkDurationNanos);
+    out->traceThreads(tids);
+    out->traceTargetDuration(initialTargetWorkDurationNanos);
+    out->tracePowerEfficient(false);
+    return out;
 }
 
 int64_t APerformanceHintManager::getPreferredRateNanos() const {
@@ -174,6 +193,7 @@
                                                         ndk::enum_range<AidlSessionHint>().end()};
 
     mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+    mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
 }
 
 APerformanceHintSession::~APerformanceHintSession() {
@@ -200,6 +220,8 @@
      * as they are most likely obsolete.
      */
     mActualWorkDurations.clear();
+    traceBatchSize(0);
+    traceTargetDuration(targetDurationNanos);
     mFirstTargetMetTimestamp = 0;
     mLastTargetMetTimestamp = 0;
     return 0;
@@ -254,6 +276,9 @@
         }
         return EPIPE;
     }
+
+    traceThreads(tids);
+
     return 0;
 }
 
@@ -289,6 +314,7 @@
               ret.exceptionMessage().c_str());
         return EPIPE;
     }
+    tracePowerEfficient(enabled);
     return OK;
 }
 
@@ -318,6 +344,7 @@
     int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
     int64_t now = uptimeNanos();
     workDuration->timestampNanos = now;
+    traceActualDuration(workDuration->actualTotalDurationNanos);
     mActualWorkDurations.push_back(std::move(*workDuration));
 
     if (actualTotalDurationNanos >= mTargetDurationNanos) {
@@ -335,6 +362,7 @@
          */
         if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
             now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+            traceBatchSize(mActualWorkDurations.size());
             return 0;
         }
         mLastTargetMetTimestamp = now;
@@ -346,12 +374,54 @@
               ret.exceptionMessage().c_str());
         mFirstTargetMetTimestamp = 0;
         mLastTargetMetTimestamp = 0;
+        traceBatchSize(mActualWorkDurations.size());
         return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
     }
     mActualWorkDurations.clear();
+    traceBatchSize(0);
 
     return 0;
 }
+// ===================================== Tracing helpers
+
+void APerformanceHintSession::traceThreads(std::vector<int32_t>& tids) {
+    std::set<int32_t> tidSet{tids.begin(), tids.end()};
+
+    // Disable old TID tracing
+    for (int32_t tid : mLastThreadIDs) {
+        if (!tidSet.count(tid)) {
+            std::string traceName =
+                    android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+            ATrace_setCounter(traceName.c_str(), 0);
+        }
+    }
+
+    // Add new TID tracing
+    for (int32_t tid : tids) {
+        std::string traceName =
+                android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+        ATrace_setCounter(traceName.c_str(), 1);
+    }
+
+    mLastThreadIDs = std::move(tids);
+}
+
+void APerformanceHintSession::tracePowerEfficient(bool powerEfficient) {
+    ATrace_setCounter((mSessionName + " power efficiency mode").c_str(), powerEfficient);
+}
+
+void APerformanceHintSession::traceActualDuration(int64_t actualDuration) {
+    ATrace_setCounter((mSessionName + " actual duration").c_str(), actualDuration);
+}
+
+void APerformanceHintSession::traceBatchSize(size_t batchSize) {
+    std::string traceName = StringPrintf("%s batch size", mSessionName.c_str());
+    ATrace_setCounter((mSessionName + " batch size").c_str(), batchSize);
+}
+
+void APerformanceHintSession::traceTargetDuration(int64_t targetDuration) {
+    ATrace_setCounter((mSessionName + " target duration").c_str(), targetDuration);
+}
 
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb4..1ac5db6 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    style="@style/ScrollViewStyle">
+    style="@style/ScrollViewStyle"
+    android:importantForAccessibility="no">
 
     <LinearLayout
-        android:id="@+id/data_transfer_confirmation"
-        style="@style/ContainerLayout">
-
-        <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
-        <ImageView
-            android:id="@+id/header_icon"
-            android:layout_width="match_parent"
-            android:layout_height="32dp"
-            android:gravity="center"
-            android:layout_marginTop="18dp"
-            android:src="@drawable/ic_warning"
-            android:contentDescription="@null" />
-
-        <LinearLayout style="@style/Description">
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/DescriptionTitle" />
-
-            <TextView
-                android:id="@+id/summary"
-                style="@style/DescriptionSummary" />
-
-        </LinearLayout>
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:importantForAccessibility="no">
 
         <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:layout_marginTop="12dp"
-            android:layout_marginBottom="18dp">
+            android:id="@+id/data_transfer_confirmation"
+            style="@style/ContainerLayout">
 
-            <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+            <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
 
-            <Button
-                android:id="@+id/btn_positive"
-                style="@style/PositiveButton"
-                android:text="@string/consent_yes" />
+            <ImageView
+                android:id="@+id/header_icon"
+                android:layout_width="match_parent"
+                android:layout_height="32dp"
+                android:gravity="center"
+                android:layout_marginTop="18dp"
+                android:src="@drawable/ic_warning"
+                android:contentDescription="@null" />
 
-            <Button
-                android:id="@+id/btn_negative"
-                style="@style/NegativeButton"
-                android:text="@string/consent_no" />
+            <LinearLayout style="@style/Description">
+
+                <TextView
+                    android:id="@+id/title"
+                    style="@style/DescriptionTitle" />
+
+                <TextView
+                    android:id="@+id/summary"
+                    style="@style/DescriptionSummary" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:layout_marginTop="12dp"
+                android:layout_marginBottom="18dp">
+
+                <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+                <Button
+                    android:id="@+id/btn_positive"
+                    style="@style/PositiveButton"
+                    android:text="@string/consent_yes" />
+
+                <Button
+                    android:id="@+id/btn_negative"
+                    style="@style/NegativeButton"
+                    android:text="@string/consent_no" />
+
+            </LinearLayout>
 
         </LinearLayout>
 
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11..6a4bb21 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@
     @UsesReflection({
             // As the runtime class name is used to generate the returned name, and the returned
             // name may be used used with reflection, generate the necessary keep rules.
-            @KeepTarget(classConstant = LocalTransport.class),
-            @KeepTarget(extendsClassConstant = LocalTransport.class)
+            @KeepTarget(instanceOfClassConstant = LocalTransport.class)
     })
     @Override
     public String name() {
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 25ad9b8..98a5a67 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -35,7 +35,10 @@
     name: "PackageInstaller",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
@@ -62,7 +65,10 @@
     name: "PackageInstaller_tablet",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
@@ -91,7 +97,10 @@
     name: "PackageInstaller_tv",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
deleted file mode 100644
index c8175ad..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
+++ /dev/null
@@ -1,912 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
-import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
-import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.InstallSourceInfo;
-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.ApplicationInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.InstallEventReceiver;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import java.io.File;
-import java.io.IOException;
-
-public class InstallRepository {
-
-    public static final String EXTRA_STAGED_SESSION_ID =
-        "com.android.packageinstaller.extra.STAGED_SESSION_ID";
-    private static final String SCHEME_PACKAGE = "package";
-    private static final String BROADCAST_ACTION =
-        "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
-    private static final String TAG = InstallRepository.class.getSimpleName();
-    private final Context mContext;
-    private final PackageManager mPackageManager;
-    private final PackageInstaller mPackageInstaller;
-    private final UserManager mUserManager;
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final AppOpsManager mAppOpsManager;
-    private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
-    private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>();
-    private final boolean mLocalLOGV = false;
-    private Intent mIntent;
-    private boolean mIsSessionInstall;
-    private boolean mIsTrustedSource;
-    /**
-     * Session ID for a session created when caller uses PackageInstaller APIs
-     */
-    private int mSessionId;
-    /**
-     * Session ID for a session created by this app
-     */
-    private int mStagedSessionId = SessionInfo.INVALID_ID;
-    private int mCallingUid;
-    private String mCallingPackage;
-    private SessionStager mSessionStager;
-    private AppOpRequestInfo mAppOpRequestInfo;
-    private AppSnippet mAppSnippet;
-    /**
-     * PackageInfo of the app being installed on device.
-     */
-    private PackageInfo mNewPackageInfo;
-
-    public InstallRepository(Context context) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mPackageInstaller = mPackageManager.getPackageInstaller();
-        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
-        mUserManager = context.getSystemService(UserManager.class);
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
-    }
-
-    /**
-     * Extracts information from the incoming install intent, checks caller's permission to install
-     * packages, verifies that the caller is the install session owner (in case of a session based
-     * install) and checks if the current user has restrictions set that prevent app installation,
-     *
-     * @param intent the incoming {@link Intent} object for installing a package
-     * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName
-     * @return <p>{@link InstallAborted} if there are errors while performing the checks</p>
-     *     <p>{@link InstallStaging} after successfully performing the checks</p>
-     */
-    public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) {
-        mIntent = intent;
-
-        String callingAttributionTag = null;
-
-        mIsSessionInstall =
-            PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
-                || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
-
-        mSessionId = mIsSessionInstall
-            ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
-            : SessionInfo.INVALID_ID;
-
-        mStagedSessionId = mIntent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID);
-
-        mCallingPackage = callerInfo.getPackageName();
-
-        if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) {
-            PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId);
-            mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
-            callingAttributionTag =
-                (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
-        }
-
-        // Uid of the source package, coming from ActivityManager
-        mCallingUid = callerInfo.getUid();
-        if (mCallingUid == Process.INVALID_UID) {
-            Log.e(TAG, "Could not determine the launching uid.");
-        }
-        final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
-        // Uid of the source package, with a preference to uid from ApplicationInfo
-        final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
-        mAppOpRequestInfo = new AppOpRequestInfo(
-            getPackageNameForUid(mContext, originatingUid, mCallingPackage),
-            originatingUid, callingAttributionTag);
-
-        if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
-            // Caller's identity could not be determined. Abort the install
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        if ((mSessionId != SessionInfo.INVALID_ID
-            && !isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId))
-            || (mStagedSessionId != SessionInfo.INVALID_ID
-            && !isCallerSessionOwner(mPackageInstaller, Process.myUid(), mStagedSessionId))) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid);
-
-        if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid,
-            mIsTrustedSource)) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        String restriction = getDevicePolicyRestrictions();
-        if (restriction != null) {
-            InstallAborted.Builder abortedBuilder =
-                new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction);
-            final Intent adminSupportDetailsIntent =
-                mDevicePolicyManager.createAdminSupportIntent(restriction);
-            if (adminSupportDetailsIntent != null) {
-                abortedBuilder.setResultIntent(adminSupportDetailsIntent);
-            }
-            return abortedBuilder.build();
-        }
-
-        maybeRemoveInvalidInstallerPackageName(callerInfo);
-
-        return new InstallStaging();
-    }
-
-    /**
-     * @return the ApplicationInfo for the installation source (the calling package), if available
-     */
-    @Nullable
-    private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
-        if (callingPackage == null) {
-            return null;
-        }
-        try {
-            return mPackageManager.getApplicationInfo(callingPackage, 0);
-        } catch (PackageManager.NameNotFoundException ignored) {
-            return null;
-        }
-    }
-
-    private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent,
-        int originatingUid) {
-        boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
-        return sourceInfo != null && sourceInfo.isPrivilegedApp()
-            && (isNotUnknownSource
-            || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid));
-    }
-
-    private String getDevicePolicyRestrictions() {
-        final String[] restrictions = new String[]{
-            UserManager.DISALLOW_INSTALL_APPS,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
-        };
-
-        for (String restriction : restrictions) {
-            if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
-                continue;
-            }
-            return restriction;
-        }
-        return null;
-    }
-
-    private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) {
-        final String installerPackageNameFromIntent =
-            mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
-        if (installerPackageNameFromIntent == null) {
-            return;
-        }
-        if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName())
-            && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES,
-            callerInfo.getPackageName())) {
-            Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
-                + " is invalid. Remove it.");
-            EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(),
-                "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
-            mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
-        }
-    }
-
-    public void stageForInstall() {
-        Uri uri = mIntent.getData();
-        if (mStagedSessionId != SessionInfo.INVALID_ID
-            || mIsSessionInstall
-            || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
-            // For a session based install or installing with a package:// URI, there is no file
-            // for us to stage.
-            mStagingResult.setValue(new InstallReady());
-            return;
-        }
-        if (uri != null
-            && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
-            && canPackageQuery(mContext, mCallingUid, uri)) {
-
-            if (mStagedSessionId > 0) {
-                final PackageInstaller.SessionInfo info =
-                    mPackageInstaller.getSessionInfo(mStagedSessionId);
-                if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
-                    Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
-                    if (info != null) {
-                        cleanupStagingSession();
-                    }
-                    mStagedSessionId = 0;
-                }
-            }
-
-            // Session does not exist, or became invalid.
-            if (mStagedSessionId <= 0) {
-                // Create session here to be able to show error.
-                try (final AssetFileDescriptor afd =
-                    mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
-                    ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
-                    PackageInstaller.SessionParams params =
-                        createSessionParams(mIntent, pfd, uri.toString());
-                    mStagedSessionId = mPackageInstaller.createSession(params);
-                } catch (IOException e) {
-                    Log.w(TAG, "Failed to create a staging session", e);
-                    mStagingResult.setValue(
-                        new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                            .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                                PackageManager.INSTALL_FAILED_INVALID_APK))
-                            .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                            .build());
-                    return;
-                }
-            }
-
-            SessionStageListener listener = new SessionStageListener() {
-                @Override
-                public void onStagingSuccess(SessionInfo info) {
-                    //TODO: Verify if the returned sessionInfo should be used anywhere
-                    mStagingResult.setValue(new InstallReady());
-                }
-
-                @Override
-                public void onStagingFailure() {
-                    cleanupStagingSession();
-                    mStagingResult.setValue(
-                        new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                            .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                                PackageManager.INSTALL_FAILED_INVALID_APK))
-                            .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                            .build());
-                }
-            };
-            if (mSessionStager != null) {
-                mSessionStager.cancel(true);
-            }
-            mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener);
-            mSessionStager.execute();
-        }
-    }
-
-    public int getStagedSessionId() {
-        return mStagedSessionId;
-    }
-
-    private void cleanupStagingSession() {
-        if (mStagedSessionId > 0) {
-            try {
-                mPackageInstaller.abandonSession(mStagedSessionId);
-            } catch (SecurityException ignored) {
-            }
-            mStagedSessionId = 0;
-        }
-    }
-
-    private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent,
-        @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class);
-        params.setPackageSource(
-            referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
-        params.setInstallAsInstantApp(false);
-        params.setReferrerUri(referrerUri);
-        params.setOriginatingUri(
-            intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class));
-        params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-            Process.INVALID_UID));
-        params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));
-        params.setInstallReason(PackageManager.INSTALL_REASON_USER);
-        // Disable full screen intent usage by for sideloads.
-        params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
-            PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
-
-        if (pfd != null) {
-            try {
-                final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd,
-                    debugPathName, 0);
-                params.setAppPackageName(result.getPackageName());
-                params.setInstallLocation(result.getInstallLocation());
-                params.setSize(result.calculateInstalledSize(params, pfd));
-            } catch (PackageInstaller.PackageParsingException e) {
-                Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
-                params.setSize(pfd.getStatSize());
-            } catch (IOException e) {
-                Log.e(TAG,
-                    "Cannot calculate installed size " + debugPathName
-                        + ". Try only apk size.", e);
-            }
-        } else {
-            Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
-        }
-        return params;
-    }
-
-    /**
-     * Processes Install session, file:// or package:// URI to generate data pertaining to user
-     * confirmation for an install. This method also checks if the source app has the AppOp granted
-     * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
-     * be reused once appOp has been granted
-     *
-     * @return <ul>
-     *     <li>InstallAborted </li>
-     *         <ul>
-     *             <li> If install session is invalid (not sealed or resolvedBaseApk path
-     *             is invalid) </li>
-     *             <li> Source app doesn't have visibility to target app </li>
-     *             <li> The APK is invalid </li>
-     *             <li> URI is invalid </li>
-     *             <li> Can't get ApplicationInfo for source app, to request AppOp </li>
-     *         </ul>
-     *    <li> InstallUserActionRequired</li>
-     *         <ul>
-     *             <li> If AppOP is granted and user action is required to proceed
-     *             with install </li>
-     *             <li> If AppOp grant is to be requested from the user</li>
-     *         </ul>
-     *  </ul>
-     */
-    public InstallStage requestUserConfirmation() {
-        if (mIsTrustedSource) {
-            if (mLocalLOGV) {
-                Log.i(TAG, "install allowed");
-            }
-            // Returns InstallUserActionRequired stage if install details could be successfully
-            // computed, else it returns InstallAborted.
-            return generateConfirmationSnippet();
-        } else {
-            InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
-            if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
-                // Source app already has appOp granted.
-                return generateConfirmationSnippet();
-            } else {
-                return unknownSourceStage;
-            }
-        }
-    }
-
-
-    private InstallStage generateConfirmationSnippet() {
-        final Object packageSource;
-        int pendingUserActionReason = -1;
-        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(mIntent.getAction())) {
-            final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
-
-            if (info == null || !info.isSealed() || resolvedPath == null) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-            packageSource = Uri.fromFile(new File(resolvedPath));
-            // TODO: Not sure where is this used yet. PIA.java passes it to
-            //  InstallInstalling if not null
-            // mOriginatingURI = null;
-            // mReferrerURI = null;
-            pendingUserActionReason = info.getPendingUserActionReason();
-        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(mIntent.getAction())) {
-            final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-
-            if (info == null || !info.isPreApprovalRequested()) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-            packageSource = info;
-            // mOriginatingURI = null;
-            // mReferrerURI = null;
-            pendingUserActionReason = info.getPendingUserActionReason();
-        } else {
-            // Two possible origins:
-            // 1. Installation with SCHEME_PACKAGE.
-            // 2. Installation with "file://" for session created by this app
-            if (mIntent.getData() != null && mIntent.getData().getScheme().equals(SCHEME_PACKAGE)) {
-                packageSource = mIntent.getData();
-            } else {
-                SessionInfo stagedSessionInfo = mPackageInstaller.getSessionInfo(mStagedSessionId);
-                packageSource = Uri.fromFile(new File(stagedSessionInfo.getResolvedBaseApkPath()));
-            }
-            // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
-            // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
-            pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
-        }
-
-        // if there's nothing to do, quietly slip into the ether
-        if (packageSource == null) {
-            Log.w(TAG, "Unspecified source");
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                    PackageManager.INSTALL_FAILED_INVALID_URI))
-                .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                .build();
-        }
-
-        return processAppSnippet(packageSource, pendingUserActionReason);
-    }
-
-    /**
-     * 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 InstallStage processAppSnippet(Object source, int userActionReason) {
-        if (source instanceof Uri) {
-            return processPackageUri((Uri) source, userActionReason);
-        } else if (source instanceof SessionInfo) {
-            return processSessionInfo((SessionInfo) source, userActionReason);
-        }
-        return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-    }
-
-    /**
-     * Parse the Uri and set up the installer for this package.
-     *
-     * @param packageUri The URI to parse
-     * @return {@code true} iff the installer could be set up
-     */
-    private InstallStage processPackageUri(final Uri packageUri, int userActionReason) {
-        final String scheme = packageUri.getScheme();
-        final String packageName = packageUri.getSchemeSpecificPart();
-
-        if (scheme == null) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        if (mLocalLOGV) {
-            Log.i(TAG, "processPackageUri(): uri = " + packageUri + ", scheme = " + scheme);
-        }
-
-        switch (scheme) {
-            case SCHEME_PACKAGE -> {
-                for (UserHandle handle : mUserManager.getUserHandles(true)) {
-                    PackageManager pmForUser = mContext.createContextAsUser(handle, 0)
-                        .getPackageManager();
-                    try {
-                        if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
-                            mNewPackageInfo = pmForUser.getPackageInfo(packageName,
-                                PackageManager.GET_PERMISSIONS
-                                    | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                        }
-                    } catch (NameNotFoundException ignored) {
-                    }
-                }
-                if (mNewPackageInfo == null) {
-                    Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart()
-                        + " not available. Discontinuing installation");
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                        .setErrorDialogType(DLG_PACKAGE_ERROR)
-                        .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                            PackageManager.INSTALL_FAILED_INVALID_APK))
-                        .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                        .build();
-                }
-                mAppSnippet = getAppSnippet(mContext, mNewPackageInfo);
-                if (mLocalLOGV) {
-                    Log.i(TAG, "Created snippet for " + mAppSnippet.getLabel());
-                }
-            }
-            case ContentResolver.SCHEME_FILE -> {
-                File sourceFile = new File(packageUri.getPath());
-                mNewPackageInfo = getPackageInfo(mContext, sourceFile,
-                    PackageManager.GET_PERMISSIONS);
-
-                // Check for parse errors
-                if (mNewPackageInfo == null) {
-                    Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                        .setErrorDialogType(DLG_PACKAGE_ERROR)
-                        .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                            PackageManager.INSTALL_FAILED_INVALID_APK))
-                        .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                        .build();
-                }
-                if (mLocalLOGV) {
-                    Log.i(TAG, "Creating snippet for local file " + sourceFile);
-                }
-                mAppSnippet = getAppSnippet(mContext, mNewPackageInfo.applicationInfo, sourceFile);
-            }
-            default -> {
-                Log.e(TAG, "Unexpected URI scheme " + packageUri);
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-        }
-
-        return new InstallUserActionRequired.Builder(
-            USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
-            .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
-            .setAppUpdating(isAppUpdating(mNewPackageInfo))
-            .build();
-    }
-
-    /**
-     * Use the SessionInfo and set up the installer for pre-commit install session.
-     *
-     * @param sessionInfo The SessionInfo to compose
-     * @return {@code true} iff the installer could be set up
-     */
-    private InstallStage processSessionInfo(@NonNull SessionInfo sessionInfo,
-        int userActionReason) {
-        mNewPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName());
-
-        mAppSnippet = getAppSnippet(mContext, sessionInfo);
-        return new InstallUserActionRequired.Builder(
-            USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
-            .setAppUpdating(isAppUpdating(mNewPackageInfo))
-            .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
-            .build();
-    }
-
-    private String getUpdateMessage(PackageInfo pkgInfo, int userActionReason) {
-        if (isAppUpdating(pkgInfo)) {
-            final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo);
-            final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
-
-            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
-                && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
-                return mContext.getString(R.string.install_confirm_question_update_owner_reminder,
-                    requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
-            }
-        }
-        return null;
-    }
-
-    private CharSequence getExistingUpdateOwnerLabel(PackageInfo pkgInfo) {
-        try {
-            final String packageName = pkgInfo.packageName;
-            final InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName);
-            final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
-            return getApplicationLabel(existingUpdateOwner);
-        } catch (NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    private CharSequence getApplicationLabel(String packageName) {
-        try {
-            final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
-                ApplicationInfoFlags.of(0));
-            return mPackageManager.getApplicationLabel(appInfo);
-        } catch (NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    private boolean isAppUpdating(PackageInfo newPkgInfo) {
-        String pkgName = newPkgInfo.packageName;
-        // Check if there is already a package on the device with this name
-        // but it has been renamed to something else.
-        String[] oldName = mPackageManager.canonicalToCurrentPackageNames(new String[]{pkgName});
-        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
-            pkgName = oldName[0];
-            newPkgInfo.packageName = pkgName;
-            newPkgInfo.applicationInfo.packageName = pkgName;
-        }
-        // Check if package is already installed. display confirmation dialog if replacing pkg
-        try {
-            // This is a little convoluted because we want to get all uninstalled
-            // apps, but this may include apps with just data, and if it is just
-            // data we still want to count it as "installed".
-            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkgName,
-                PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                return false;
-            }
-        } catch (NameNotFoundException e) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Once the user returns from Settings related to installing from unknown sources, reattempt
-     * the installation if the source app is granted permission to install other apps. Abort the
-     * installation if the source app is still not granted installing permission.
-     * @return {@link InstallUserActionRequired} containing data required to ask user confirmation
-     * to proceed with the install.
-     * {@link InstallAborted} if there was an error while recomputing, or the source still
-     * doesn't have install permission.
-     */
-    public InstallStage reattemptInstall() {
-        InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
-        if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
-            // Source app now has appOp granted.
-            return generateConfirmationSnippet();
-        } else if (unknownSourceStage.getStageCode() == InstallStage.STAGE_ABORTED) {
-            // There was some error in determining the AppOp code for the source app.
-            // Abort installation
-            return unknownSourceStage;
-        } else {
-            // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
-            // unexpected while reattempting the install. Let's abort it.
-            Log.e(TAG, "AppOp still not granted.");
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-    }
-
-    private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) {
-        if (requestInfo.getCallingPackage() == null) {
-            Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName);
-            return new InstallUserActionRequired.Builder(
-                USER_ACTION_REASON_ANONYMOUS_SOURCE, null)
-                .build();
-        }
-        // Shouldn't use static constant directly, see b/65534401.
-        final String appOpStr =
-            AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr,
-            requestInfo.getOriginatingUid(),
-            requestInfo.getCallingPackage(), requestInfo.getAttributionTag(),
-            "Started package installation activity");
-
-        if (mLocalLOGV) {
-            Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
-        }
-        switch (appOpMode) {
-            case AppOpsManager.MODE_DEFAULT:
-                mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(),
-                    requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED);
-                // fall through
-            case AppOpsManager.MODE_ERRORED:
-                try {
-                    ApplicationInfo sourceInfo =
-                        mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0);
-                    AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo);
-                    return new InstallUserActionRequired.Builder(
-                        USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet)
-                        .setDialogMessage(requestInfo.getCallingPackage())
-                        .build();
-                } catch (NameNotFoundException e) {
-                    Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage());
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-                }
-            case AppOpsManager.MODE_ALLOWED:
-                return new InstallReady();
-            default:
-                Log.e(TAG, "Invalid app op mode " + appOpMode
-                    + " for OP_REQUEST_INSTALL_PACKAGES found for uid "
-                    + requestInfo.getOriginatingUid());
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-    }
-
-
-    /**
-     * Kick off the installation. Register a broadcast listener to get the result of the
-     * installation and commit the staged session here. If the installation was session based,
-     * signal the PackageInstaller that the user has granted permission to proceed with the install
-     */
-    public void initiateInstall() {
-        if (mSessionId > 0) {
-            mPackageInstaller.setPermissionsResult(mSessionId, true);
-            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE)
-                .setActivityResultCode(Activity.RESULT_OK).build());
-            return;
-        }
-
-        Uri uri = mIntent.getData();
-        if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) {
-            try {
-                mPackageManager.installExistingPackage(mNewPackageInfo.packageName);
-                setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1);
-            } catch (PackageManager.NameNotFoundException e) {
-                setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                    PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-            }
-            return;
-        }
-
-        if (mStagedSessionId <= 0) {
-            // How did we even land here?
-            Log.e(TAG, "Invalid local session and caller initiated session");
-            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                .build());
-            return;
-        }
-
-        int installId;
-        try {
-            mInstallResult.setValue(new InstallInstalling(mAppSnippet));
-            installId = InstallEventReceiver.addObserver(mContext,
-                EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult);
-        } catch (EventResultPersister.OutOfIdsException e) {
-            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-            return;
-        }
-
-        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
-        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        broadcastIntent.setPackage(mContext.getPackageName());
-        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId);
-
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(
-            mContext, installId, broadcastIntent,
-            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
-        try {
-            PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId);
-            session.commit(pendingIntent.getIntentSender());
-        } catch (Exception e) {
-            Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e);
-            mPackageInstaller.abandonSession(mStagedSessionId);
-            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-        }
-    }
-
-    private void setStageBasedOnResult(int statusCode, int legacyStatus, String message,
-        int serviceId) {
-        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
-            boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-
-            InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet)
-                .setShouldReturnResult(shouldReturnResult);
-            Intent resultIntent;
-            if (shouldReturnResult) {
-                resultIntent = new Intent()
-                    .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
-            } else {
-                resultIntent = mPackageManager
-                    .getLaunchIntentForPackage(mNewPackageInfo.packageName);
-            }
-            successBuilder.setResultIntent(resultIntent);
-
-            mInstallResult.setValue(successBuilder.build());
-        } else {
-            mInstallResult.setValue(
-                new InstallFailed(mAppSnippet, statusCode, legacyStatus, message));
-        }
-    }
-
-    public MutableLiveData<InstallStage> getInstallResult() {
-        return mInstallResult;
-    }
-
-    /**
-     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
-     * be aborted
-     */
-    public void cleanupInstall() {
-        if (mSessionId > 0) {
-            mPackageInstaller.setPermissionsResult(mSessionId, false);
-        } else if (mStagedSessionId > 0) {
-            cleanupStagingSession();
-        }
-    }
-
-    /**
-     * When the identity of the install source could not be determined, user can skip checking the
-     * source and directly proceed with the install.
-     */
-    public InstallStage forcedSkipSourceCheck() {
-        return generateConfirmationSnippet();
-    }
-
-    public MutableLiveData<Integer> getStagingProgress() {
-        if (mSessionStager != null) {
-            return mSessionStager.getProgress();
-        }
-        return new MutableLiveData<>(0);
-    }
-
-    public MutableLiveData<InstallStage> getStagingResult() {
-        return mStagingResult;
-    }
-
-    public interface SessionStageListener {
-
-        void onStagingSuccess(SessionInfo info);
-
-        void onStagingFailure();
-    }
-
-    public static class CallerInfo {
-
-        private final String mPackageName;
-        private final int mUid;
-
-        public CallerInfo(String packageName, int uid) {
-            mPackageName = packageName;
-            mUid = uid;
-        }
-
-        public String getPackageName() {
-            return mPackageName;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-    }
-
-    public static class AppOpRequestInfo {
-
-        private String mCallingPackage;
-        private String mAttributionTag;
-        private int mOrginatingUid;
-
-        public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) {
-            mCallingPackage = callingPackage;
-            mOrginatingUid = orginatingUid;
-            mAttributionTag = attributionTag;
-        }
-
-        public String getCallingPackage() {
-            return mCallingPackage;
-        }
-
-        public String getAttributionTag() {
-            return mAttributionTag;
-        }
-
-        public int getOriginatingUid() {
-            return mOrginatingUid;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
new file mode 100644
index 0000000..326e533
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.os.Process
+import android.os.UserManager
+import android.text.TextUtils
+import android.util.EventLog
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.InstallEventReceiver
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_UNKNOWN_SOURCE
+import com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery
+import com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner
+import com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import java.io.File
+import java.io.IOException
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+class InstallRepository(private val context: Context) {
+
+    private val packageManager: PackageManager = context.packageManager
+    private val packageInstaller: PackageInstaller = packageManager.packageInstaller
+    private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+    private val devicePolicyManager: DevicePolicyManager? =
+        context.getSystemService(DevicePolicyManager::class.java)
+    private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+    private val localLOGV = false
+    private var isSessionInstall = false
+    private var isTrustedSource = false
+    private val _stagingResult = MutableLiveData<InstallStage>()
+    val stagingResult: LiveData<InstallStage>
+        get() = _stagingResult
+    private val _installResult = MutableLiveData<InstallStage>()
+    val installResult: LiveData<InstallStage>
+        get() = _installResult
+
+    /**
+     * Session ID for a session created when caller uses PackageInstaller APIs
+     */
+    private var sessionId = SessionInfo.INVALID_ID
+
+    /**
+     * Session ID for a session created by this app
+     */
+    var stagedSessionId = SessionInfo.INVALID_ID
+        private set
+    private var callingUid = Process.INVALID_UID
+    private var callingPackage: String? = null
+    private var sessionStager: SessionStager? = null
+    private lateinit var intent: Intent
+    private lateinit var appOpRequestInfo: AppOpRequestInfo
+    private lateinit var appSnippet: PackageUtil.AppSnippet
+
+    /**
+     * PackageInfo of the app being installed on device.
+     */
+    private var newPackageInfo: PackageInfo? = null
+
+    /**
+     * Extracts information from the incoming install intent, checks caller's permission to install
+     * packages, verifies that the caller is the install session owner (in case of a session based
+     * install) and checks if the current user has restrictions set that prevent app installation,
+     *
+     * @param intent the incoming [Intent] object for installing a package
+     * @param callerInfo [CallerInfo] that holds the callingUid and callingPackageName
+     * @return
+     *  * [InstallAborted] if there are errors while performing the checks
+     *  * [InstallStaging] after successfully performing the checks
+     */
+    fun performPreInstallChecks(intent: Intent, callerInfo: CallerInfo): InstallStage {
+        this.intent = intent
+
+        var callingAttributionTag: String? = null
+
+        isSessionInstall =
+            PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action
+                || PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action
+
+        sessionId = if (isSessionInstall)
+            intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
+        else SessionInfo.INVALID_ID
+
+        stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
+
+        callingPackage = callerInfo.packageName
+
+        if (callingPackage == null && sessionId != SessionInfo.INVALID_ID) {
+            val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
+            callingPackage = sessionInfo?.getInstallerPackageName()
+            callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
+        }
+
+        // Uid of the source package, coming from ActivityManager
+        callingUid = callerInfo.uid
+        if (callingUid == Process.INVALID_UID) {
+            Log.e(LOG_TAG, "Could not determine the launching uid.")
+        }
+        val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
+        // Uid of the source package, with a preference to uid from ApplicationInfo
+        val originatingUid = sourceInfo?.uid ?: callingUid
+        appOpRequestInfo = AppOpRequestInfo(
+            getPackageNameForUid(context, originatingUid, callingPackage),
+            originatingUid, callingAttributionTag
+        )
+
+        if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+            // Caller's identity could not be determined. Abort the install
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        if ((sessionId != SessionInfo.INVALID_ID
+                && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+            || (stagedSessionId != SessionInfo.INVALID_ID
+                && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
+        ) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
+        if (!isInstallPermissionGrantedOrRequested(
+                context, callingUid, originatingUid, isTrustedSource
+            )
+        ) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        val restriction = getDevicePolicyRestrictions()
+        if (restriction != null) {
+            val adminSupportDetailsIntent =
+                devicePolicyManager!!.createAdminSupportIntent(restriction)
+            return InstallAborted(
+                ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
+            )
+        }
+
+        maybeRemoveInvalidInstallerPackageName(callerInfo)
+
+        return InstallStaging()
+    }
+
+    /**
+     * @return the ApplicationInfo for the installation source (the calling package), if available
+     */
+    private fun getSourceInfo(callingPackage: String?): ApplicationInfo? {
+        return try {
+            callingPackage?.let { packageManager.getApplicationInfo(it, 0) }
+        } catch (ignored: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun isInstallRequestFromTrustedSource(
+        sourceInfo: ApplicationInfo?,
+        intent: Intent,
+        originatingUid: Int,
+    ): Boolean {
+        val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+        return (sourceInfo != null && sourceInfo.isPrivilegedApp
+            && (isNotUnknownSource
+            || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+    }
+
+    private fun getDevicePolicyRestrictions(): String? {
+        val restrictions = arrayOf(
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+        )
+        for (restriction in restrictions) {
+            if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+                continue
+            }
+            return restriction
+        }
+        return null
+    }
+
+    private fun maybeRemoveInvalidInstallerPackageName(callerInfo: CallerInfo) {
+        val installerPackageNameFromIntent =
+            intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) ?: return
+
+        if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.packageName)
+            && callerInfo.packageName != null
+            && isPermissionGranted(
+                packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName
+            )
+        ) {
+            Log.e(
+                LOG_TAG, "The given installer package name $installerPackageNameFromIntent"
+                    + " is invalid. Remove it."
+            )
+            EventLog.writeEvent(
+                0x534e4554, "236687884", callerInfo.uid,
+                "Invalid EXTRA_INSTALLER_PACKAGE_NAME"
+            )
+            intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)
+        }
+    }
+
+    @OptIn(DelicateCoroutinesApi::class)
+    fun stageForInstall() {
+        val uri = intent.data
+        if (stagedSessionId != SessionInfo.INVALID_ID
+            || isSessionInstall
+            || (uri != null && SCHEME_PACKAGE == uri.scheme)
+        ) {
+            // For a session based install or installing with a package:// URI, there is no file
+            // for us to stage.
+            _stagingResult.value = InstallReady()
+            return
+        }
+        if (uri != null
+            && ContentResolver.SCHEME_CONTENT == uri.scheme
+            && canPackageQuery(context, callingUid, uri)
+        ) {
+            if (stagedSessionId > 0) {
+                val info: SessionInfo? = packageInstaller.getSessionInfo(stagedSessionId)
+                if (info == null || !info.isActive || info.resolvedBaseApkPath == null) {
+                    Log.w(LOG_TAG, "Session $stagedSessionId in funky state; ignoring")
+                    if (info != null) {
+                        cleanupStagingSession()
+                    }
+                    stagedSessionId = 0
+                }
+            }
+
+            // Session does not exist, or became invalid.
+            if (stagedSessionId <= 0) {
+                // Create session here to be able to show error.
+                try {
+                    context.contentResolver.openAssetFileDescriptor(uri, "r").use { afd ->
+                        val pfd: ParcelFileDescriptor? = afd?.parcelFileDescriptor
+                        val params: SessionParams =
+                            createSessionParams(intent, pfd, uri.toString())
+                        stagedSessionId = packageInstaller.createSession(params)
+                    }
+                } catch (e: IOException) {
+                    Log.w(LOG_TAG, "Failed to create a staging session", e)
+                    _stagingResult.value = InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                    return
+                }
+            }
+
+            sessionStager = SessionStager(context, uri, stagedSessionId)
+            GlobalScope.launch(Dispatchers.Main) {
+                val wasFileStaged = sessionStager!!.execute()
+
+                if (wasFileStaged) {
+                    _stagingResult.value = InstallReady()
+                } else {
+                    cleanupStagingSession()
+                    _stagingResult.value = InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+            }
+        }
+    }
+
+    private fun cleanupStagingSession() {
+        if (stagedSessionId > 0) {
+            try {
+                packageInstaller.abandonSession(stagedSessionId)
+            } catch (ignored: SecurityException) {
+            }
+            stagedSessionId = 0
+        }
+    }
+
+    private fun createSessionParams(
+        intent: Intent,
+        pfd: ParcelFileDescriptor?,
+        debugPathName: String,
+    ): SessionParams {
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+        val referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
+        params.setPackageSource(
+            if (referrerUri != null)
+                PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+            else PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+        )
+        params.setInstallAsInstantApp(false)
+        params.setReferrerUri(referrerUri)
+        params.setOriginatingUri(
+            intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri::class.java)
+        )
+        params.setOriginatingUid(
+            intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, Process.INVALID_UID)
+        )
+        params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME))
+        params.setInstallReason(PackageManager.INSTALL_REASON_USER)
+        // Disable full screen intent usage by for sideloads.
+        params.setPermissionState(
+            Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
+        )
+        if (pfd != null) {
+            try {
+                val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
+                params.setAppPackageName(installInfo.packageName)
+                params.setInstallLocation(installInfo.installLocation)
+                params.setSize(installInfo.calculateInstalledSize(params, pfd))
+            } catch (e: PackageInstaller.PackageParsingException) {
+                Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
+                params.setSize(pfd.statSize)
+            } catch (e: IOException) {
+                Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+                    "Try only apk size.", e
+                )
+            }
+        } else {
+            Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.")
+        }
+        return params
+    }
+
+    /**
+     * Processes Install session, file:// or package:// URI to generate data pertaining to user
+     * confirmation for an install. This method also checks if the source app has the AppOp granted
+     * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
+     * be reused once appOp has been granted
+     *
+     * @return
+     *  * [InstallAborted]
+     *      *  If install session is invalid (not sealed or resolvedBaseApk path is invalid)
+     *      *  Source app doesn't have visibility to target app
+     *      *  The APK is invalid
+     *      *  URI is invalid
+     *      *  Can't get ApplicationInfo for source app, to request AppOp
+     *
+     *  *  [InstallUserActionRequired]
+     *      * If AppOP is granted and user action is required to proceed with install
+     *      * If AppOp grant is to be requested from the user
+     */
+    fun requestUserConfirmation(): InstallStage {
+        return if (isTrustedSource) {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "install allowed")
+            }
+            // Returns InstallUserActionRequired stage if install details could be successfully
+            // computed, else it returns InstallAborted.
+            generateConfirmationSnippet()
+        } else {
+            val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+            if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
+                // Source app already has appOp granted.
+                generateConfirmationSnippet()
+            } else {
+                unknownSourceStage
+            }
+        }
+    }
+
+    private fun generateConfirmationSnippet(): InstallStage {
+        val packageSource: Any?
+        val pendingUserActionReason: Int
+
+        if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
+            val info = packageInstaller.getSessionInfo(sessionId)
+            val resolvedPath = info?.resolvedBaseApkPath
+            if (info == null || !info.isSealed || resolvedPath == null) {
+                Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+            packageSource = Uri.fromFile(File(resolvedPath))
+            // TODO: Not sure where is this used yet. PIA.java passes it to
+            //  InstallInstalling if not null
+            // mOriginatingURI = null;
+            // mReferrerURI = null;
+            pendingUserActionReason = info.getPendingUserActionReason()
+        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action) {
+            val info = packageInstaller.getSessionInfo(sessionId)
+            if (info == null || !info.isPreApprovalRequested) {
+                Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+            packageSource = info
+            // mOriginatingURI = null;
+            // mReferrerURI = null;
+            pendingUserActionReason = info.getPendingUserActionReason()
+        } else {
+            // Two possible origins:
+            // 1. Installation with SCHEME_PACKAGE.
+            // 2. Installation with "file://" for session created by this app
+            packageSource =
+                if (intent.data?.scheme == SCHEME_PACKAGE) {
+                    intent.data
+                } else {
+                    val stagedSessionInfo = packageInstaller.getSessionInfo(stagedSessionId)
+                    Uri.fromFile(File(stagedSessionInfo?.resolvedBaseApkPath!!))
+                }
+            // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+            // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
+            pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE
+        }
+
+        // if there's nothing to do, quietly slip into the ether
+        if (packageSource == null) {
+            Log.w(LOG_TAG, "Unspecified source")
+            return InstallAborted(
+                ABORT_REASON_INTERNAL_ERROR,
+                resultIntent = Intent().putExtra(
+                    Intent.EXTRA_INSTALL_RESULT,
+                    PackageManager.INSTALL_FAILED_INVALID_URI
+                ),
+                activityResultCode = Activity.RESULT_FIRST_USER
+            )
+        }
+        return processAppSnippet(packageSource, pendingUserActionReason)
+    }
+
+    /**
+     * 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
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processAppSnippet(source: Any, userActionReason: Int): InstallStage {
+        return when (source) {
+            is Uri -> processPackageUri(source, userActionReason)
+            is SessionInfo -> processSessionInfo(source, userActionReason)
+            else -> InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+    }
+
+    /**
+     * Parse the Uri and set up the installer for this package.
+     *
+     * @param packageUri The URI to parse
+     * @return
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processPackageUri(packageUri: Uri, userActionReason: Int): InstallStage {
+        val scheme = packageUri.scheme
+        val packageName = packageUri.schemeSpecificPart
+        if (scheme == null) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+        if (localLOGV) {
+            Log.i(LOG_TAG, "processPackageUri(): uri = $packageUri, scheme = $scheme")
+        }
+        when (scheme) {
+            SCHEME_PACKAGE -> {
+                for (handle in userManager!!.getUserHandles(true)) {
+                    val pmForUser = context.createContextAsUser(handle, 0).packageManager
+                    try {
+                        if (pmForUser.canPackageQuery(callingPackage!!, packageName)) {
+                            newPackageInfo = pmForUser.getPackageInfo(
+                                packageName,
+                                PackageManager.GET_PERMISSIONS
+                                    or PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            )
+                        }
+                    } catch (ignored: PackageManager.NameNotFoundException) {
+                    }
+                }
+                if (newPackageInfo == null) {
+                    Log.w(
+                        LOG_TAG, "Requested package " + packageUri.schemeSpecificPart
+                            + " not available. Discontinuing installation"
+                    )
+                    return InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        errorDialogType = DLG_PACKAGE_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+                appSnippet = getAppSnippet(context, newPackageInfo!!)
+                if (localLOGV) {
+                    Log.i(LOG_TAG, "Created snippet for " + appSnippet.label)
+                }
+            }
+
+            ContentResolver.SCHEME_FILE -> {
+                val sourceFile = packageUri.path?.let { File(it) }
+                newPackageInfo = sourceFile?.let {
+                    getPackageInfo(context, it, PackageManager.GET_PERMISSIONS)
+                }
+
+                // Check for parse errors
+                if (newPackageInfo == null) {
+                    Log.w(
+                        LOG_TAG, "Parse error when parsing manifest. " +
+                            "Discontinuing installation"
+                    )
+                    return InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        errorDialogType = DLG_PACKAGE_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT,
+                            PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+                if (localLOGV) {
+                    Log.i(LOG_TAG, "Creating snippet for local file $sourceFile")
+                }
+                appSnippet = getAppSnippet(context, newPackageInfo!!, sourceFile!!)
+            }
+
+            else -> {
+                Log.e(LOG_TAG, "Unexpected URI scheme $packageUri")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+        return InstallUserActionRequired(
+            USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+            getUpdateMessage(newPackageInfo!!, userActionReason)
+        )
+    }
+
+    /**
+     * Use the SessionInfo and set up the installer for pre-commit install session.
+     *
+     * @param sessionInfo The SessionInfo to compose
+     * @return
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processSessionInfo(sessionInfo: SessionInfo, userActionReason: Int): InstallStage {
+        newPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName())
+        appSnippet = getAppSnippet(context, sessionInfo)
+
+        return InstallUserActionRequired(
+            USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+            getUpdateMessage(newPackageInfo!!, userActionReason)
+
+        )
+    }
+
+    private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
+        if (isAppUpdating(pkgInfo)) {
+            val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
+            val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
+                && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
+            ) {
+                return context.getString(
+                    R.string.install_confirm_question_update_owner_reminder,
+                    requestedUpdateOwnerLabel, existingUpdateOwnerLabel
+                )
+            }
+        }
+        return null
+    }
+
+    private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+        return try {
+            val packageName = pkgInfo.packageName
+            val sourceInfo = packageManager.getInstallSourceInfo(packageName)
+            val existingUpdateOwner = sourceInfo.updateOwnerPackageName
+            getApplicationLabel(existingUpdateOwner)
+        } catch (e: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun getApplicationLabel(packageName: String?): CharSequence? {
+        return try {
+            val appInfo = packageName?.let {
+                packageManager.getApplicationInfo(
+                    it, PackageManager.ApplicationInfoFlags.of(0)
+                )
+            }
+            appInfo?.let { packageManager.getApplicationLabel(it) }
+        } catch (e: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun isAppUpdating(newPkgInfo: PackageInfo): Boolean {
+        var pkgName = newPkgInfo.packageName
+        // Check if there is already a package on the device with this name
+        // but it has been renamed to something else.
+        val oldName = packageManager.canonicalToCurrentPackageNames(arrayOf(pkgName))
+        if (oldName != null && oldName.isNotEmpty() && oldName[0] != null) {
+            pkgName = oldName[0]
+            newPkgInfo.packageName = pkgName
+            newPkgInfo.applicationInfo?.packageName = pkgName
+        }
+
+        // Check if package is already installed. display confirmation dialog if replacing pkg
+        try {
+            // This is a little convoluted because we want to get all uninstalled
+            // apps, but this may include apps with just data, and if it is just
+            // data we still want to count it as "installed".
+            val appInfo = packageManager.getApplicationInfo(
+                pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES
+            )
+            if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
+                return false
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            return false
+        }
+        return true
+    }
+
+    /**
+     * Once the user returns from Settings related to installing from unknown sources, reattempt
+     * the installation if the source app is granted permission to install other apps. Abort the
+     * installation if the source app is still not granted installing permission.
+     *
+     * @return
+     * * [InstallUserActionRequired] containing data required to ask user confirmation
+     * to proceed with the install.
+     * * [InstallAborted] if there was an error while recomputing, or the source still
+     * doesn't have install permission.
+     */
+    fun reattemptInstall(): InstallStage {
+        val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+        return when (unknownSourceStage.stageCode) {
+            InstallStage.STAGE_READY -> {
+                // Source app now has appOp granted.
+                generateConfirmationSnippet()
+            }
+
+            InstallStage.STAGE_ABORTED -> {
+                // There was some error in determining the AppOp code for the source app.
+                // Abort installation
+                unknownSourceStage
+            }
+
+            else -> {
+                // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
+                // unexpected while reattempting the install. Let's abort it.
+                Log.e(LOG_TAG, "AppOp still not granted.")
+                InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+    }
+
+    private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
+        if (requestInfo.callingPackage == null) {
+            Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
+            return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
+        }
+        // Shouldn't use static constant directly, see b/65534401.
+        val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
+        val appOpMode = appOpsManager!!.noteOpNoThrow(
+            appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+            requestInfo.attributionTag, "Started package installation activity"
+        )
+        if (localLOGV) {
+            Log.i(LOG_TAG, "handleUnknownSources(): appMode=$appOpMode")
+        }
+
+        return when (appOpMode) {
+            AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
+                if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+                    appOpsManager.setMode(
+                        appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+                        AppOpsManager.MODE_ERRORED
+                    )
+                }
+                try {
+                    val sourceInfo =
+                        packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+                    val sourceAppSnippet = getAppSnippet(context, sourceInfo)
+                    InstallUserActionRequired(
+                        USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
+                        dialogMessage = requestInfo.callingPackage
+                    )
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+                    InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+                }
+            }
+
+            AppOpsManager.MODE_ALLOWED -> InstallReady()
+
+            else -> {
+                Log.e(
+                    LOG_TAG, "Invalid app op mode $appOpMode for " +
+                        "OP_REQUEST_INSTALL_PACKAGES found for uid $requestInfo.originatingUid"
+                )
+                InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+    }
+
+    /**
+     * Kick off the installation. Register a broadcast listener to get the result of the
+     * installation and commit the staged session here. If the installation was session based,
+     * signal the PackageInstaller that the user has granted permission to proceed with the install
+     */
+    fun initiateInstall() {
+        if (sessionId > 0) {
+            packageInstaller.setPermissionsResult(sessionId, true)
+            _installResult.value = InstallAborted(
+                ABORT_REASON_DONE, activityResultCode = Activity.RESULT_OK
+            )
+            return
+        }
+        val uri = intent.data
+        if (SCHEME_PACKAGE == uri?.scheme) {
+            try {
+                packageManager.installExistingPackage(
+                    newPackageInfo!!.packageName, PackageManager.INSTALL_REASON_USER
+                )
+                setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
+            } catch (e: PackageManager.NameNotFoundException) {
+                setStageBasedOnResult(
+                    PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+                    null)
+            }
+            return
+        }
+        if (stagedSessionId <= 0) {
+            // How did we even land here?
+            Log.e(LOG_TAG, "Invalid local session and caller initiated session")
+            _installResult.value = InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            return
+        }
+        val installId: Int
+        try {
+            _installResult.value = InstallInstalling(appSnippet)
+            installId = InstallEventReceiver.addObserver(
+                context, EventResultPersister.GENERATE_NEW_ID
+            ) { statusCode: Int, legacyStatus: Int, message: String?, serviceId: Int ->
+                setStageBasedOnResult(statusCode, legacyStatus, message)
+            }
+        } catch (e: OutOfIdsException) {
+            setStageBasedOnResult(
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+            return
+        }
+        val broadcastIntent = Intent(BROADCAST_ACTION)
+        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        broadcastIntent.setPackage(context.packageName)
+        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId)
+        val pendingIntent = PendingIntent.getBroadcast(
+            context, installId, broadcastIntent,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+        )
+        try {
+            val session = packageInstaller.openSession(stagedSessionId)
+            session.commit(pendingIntent.intentSender)
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
+            packageInstaller.abandonSession(stagedSessionId)
+            setStageBasedOnResult(
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+        }
+    }
+
+    private fun setStageBasedOnResult(
+        statusCode: Int,
+        legacyStatus: Int,
+        message: String?
+    ) {
+        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
+            val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+            val resultIntent = if (shouldReturnResult) {
+                Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
+            } else {
+                packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+            }
+            _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
+        } else {
+            _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+        }
+    }
+
+    /**
+     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
+     * be aborted
+     */
+    fun cleanupInstall() {
+        if (sessionId > 0) {
+            packageInstaller.setPermissionsResult(sessionId, false)
+        } else if (stagedSessionId > 0) {
+            cleanupStagingSession()
+        }
+    }
+
+    /**
+     * When the identity of the install source could not be determined, user can skip checking the
+     * source and directly proceed with the install.
+     */
+    fun forcedSkipSourceCheck(): InstallStage {
+        return generateConfirmationSnippet()
+    }
+
+    val stagingProgress: LiveData<Int>
+        get() = sessionStager?.progress ?: MutableLiveData(0)
+
+    companion object {
+        const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID"
+        const val SCHEME_PACKAGE = "package"
+        const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"
+        private val LOG_TAG = InstallRepository::class.java.simpleName
+    }
+
+    data class CallerInfo(val packageName: String?, val uid: Int)
+    data class AppOpRequestInfo(
+        val callingPackage: String?,
+        val originatingUid: Int,
+        val attributionTag: String?,
+    )
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
new file mode 100644
index 0000000..be49b39
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+
+sealed class InstallStage(val stageCode: Int) {
+
+    companion object {
+        const val STAGE_DEFAULT = -1
+        const val STAGE_ABORTED = 0
+        const val STAGE_STAGING = 1
+        const val STAGE_READY = 2
+        const val STAGE_USER_ACTION_REQUIRED = 3
+        const val STAGE_INSTALLING = 4
+        const val STAGE_SUCCESS = 5
+        const val STAGE_FAILED = 6
+    }
+}
+
+class InstallStaging : InstallStage(STAGE_STAGING)
+
+class InstallReady : InstallStage(STAGE_READY)
+
+data class InstallUserActionRequired(
+    val actionReason: Int,
+    private val appSnippet: PackageUtil.AppSnippet? = null,
+    val isAppUpdating: Boolean = false,
+    val dialogMessage: String? = null,
+) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
+
+    val appIcon: Drawable?
+        get() = appSnippet?.icon
+
+    val appLabel: String?
+        get() = appSnippet?.let { appSnippet.label as String? }
+
+    companion object {
+        const val USER_ACTION_REASON_UNKNOWN_SOURCE = 0
+        const val USER_ACTION_REASON_ANONYMOUS_SOURCE = 1
+        const val USER_ACTION_REASON_INSTALL_CONFIRMATION = 2
+    }
+}
+
+data class InstallInstalling(private val appSnippet: PackageUtil.AppSnippet) :
+    InstallStage(STAGE_INSTALLING) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallSuccess(
+    private val appSnippet: PackageUtil.AppSnippet,
+    val shouldReturnResult: Boolean = false,
+    /**
+     *
+     * * If the caller is requesting a result back, this will hold the Intent with
+     * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
+     * back to the caller.
+     *
+     * * If the caller doesn't want the result back, this will hold the Intent that launches
+     * the newly installed / updated app if a launchable activity exists.
+     */
+    val resultIntent: Intent? = null,
+) : InstallStage(STAGE_SUCCESS) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallFailed(
+    private val appSnippet: PackageUtil.AppSnippet,
+    val legacyCode: Int,
+    val statusCode: Int,
+    val message: String?,
+) : InstallStage(STAGE_FAILED) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallAborted(
+    val abortReason: Int,
+    /**
+     * It will hold the restriction name, when the restriction was enforced by the system, and not
+     * a device admin.
+     */
+    val message: String? = null,
+    /**
+     * * If abort reason is [ABORT_REASON_POLICY], then this will hold the Intent
+     * to display a support dialog when a feature was disabled by an admin. It will be
+     * `null` if the feature is disabled by the system. In this case, the restriction name
+     * will be set in [message]
+     * * If the abort reason is [ABORT_REASON_INTERNAL_ERROR], it **may** hold an
+     * intent to be sent as a result to the calling activity.
+     */
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = Activity.RESULT_CANCELED,
+    val errorDialogType: Int? = 0,
+) : InstallStage(STAGE_ABORTED) {
+
+    companion object {
+        const val ABORT_REASON_INTERNAL_ERROR = 0
+        const val ABORT_REASON_POLICY = 1
+        const val ABORT_REASON_DONE = 2
+        const val DLG_PACKAGE_ERROR = 1
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
deleted file mode 100644
index fe05237..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-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.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import java.io.File;
-import java.util.Arrays;
-import java.util.Objects;
-
-public class PackageUtil {
-
-    private static final String TAG = InstallRepository.class.getSimpleName();
-    private static final String DOWNLOADS_AUTHORITY = "downloads";
-    private static final String SPLIT_BASE_APK_END_WITH = "base.apk";
-
-    /**
-     * Determines if the UID belongs to the system downloads provider and returns the
-     * {@link ApplicationInfo} of the provider
-     *
-     * @param uid UID of the caller
-     * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a
-     *     system app, and its UID matches with the passed UID, null otherwise.
-     */
-    public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
-        final ProviderInfo providerInfo = pm.resolveContentProvider(
-            DOWNLOADS_AUTHORITY, 0);
-        if (providerInfo == null) {
-            // There seems to be no currently enabled downloads provider on the system.
-            return null;
-        }
-        ApplicationInfo appInfo = providerInfo.applicationInfo;
-        if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
-            return appInfo;
-        }
-        return null;
-    }
-
-    /**
-     * Get the maximum target sdk for a UID.
-     *
-     * @param context The context to use
-     * @param uid The UID requesting the install/uninstall
-     * @return The maximum target SDK or -1 if the uid does not match any packages.
-     */
-    public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
-        PackageManager pm = context.getPackageManager();
-        final String[] packages = pm.getPackagesForUid(uid);
-        int targetSdkVersion = -1;
-        if (packages != null) {
-            for (String packageName : packages) {
-                try {
-                    ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                    targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
-                } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore and try the next package
-                }
-            }
-        }
-        return targetSdkVersion;
-    }
-
-    public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) {
-        PackageManager pm = context.getPackageManager();
-        ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(),
-            PackageManager.ComponentInfoFlags.of(0));
-        if (info == null) {
-            return false;
-        }
-        String targetPackage = info.packageName;
-
-        String[] callingPackages = pm.getPackagesForUid(callingUid);
-        if (callingPackages == null) {
-            return false;
-        }
-        for (String callingPackage : callingPackages) {
-            try {
-                if (pm.canPackageQuery(callingPackage, targetPackage)) {
-                    return true;
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // no-op
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param context the {@link Context} object
-     * @param permission the permission name to check
-     * @param callingUid the UID of the caller who's permission is being checked
-     * @return {@code true} if the callingUid is granted the said permission
-     */
-    public static boolean isPermissionGranted(Context context, String permission, int callingUid) {
-        return context.checkPermission(permission, -1, callingUid)
-            == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * @param pm the {@link PackageManager} object
-     * @param permission the permission name to check
-     * @param packageName the name of the package who's permission is being checked
-     * @return {@code true} if the package is granted the said permission
-     */
-    public static boolean isPermissionGranted(PackageManager pm, String permission,
-        String packageName) {
-        return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * @param context the {@link Context} object
-     * @param callingUid the UID of the caller who's permission is being checked
-     * @param originatingUid the UID from where install is being originated. This could be same as
-     * callingUid or it will be the UID of the package performing a session based install
-     * @param isTrustedSource whether install request is coming from a privileged app or an app that
-     * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted
-     * @return {@code true} if the package is granted the said permission
-     */
-    public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid,
-        int originatingUid, boolean isTrustedSource) {
-        boolean isDocumentsManager =
-            isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid);
-        boolean isSystemDownloadsProvider =
-            getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null;
-
-        if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
-
-            final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid);
-            if (targetSdkVersion < 0) {
-                // Invalid originating uid supplied. Abort install.
-                Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
-                return false;
-            } else if (targetSdkVersion >= Build.VERSION_CODES.O
-                && !isUidRequestingPermission(context.getPackageManager(), originatingUid,
-                Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
-                Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
-                    + Manifest.permission.REQUEST_INSTALL_PACKAGES);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * @param pm the {@link PackageManager} object
-     * @param uid the UID of the caller who's permission is being checked
-     * @param permission the permission name to check
-     * @return {@code true} if the caller is requesting the said permission in its Manifest
-     */
-    public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) {
-        final String[] packageNames = pm.getPackagesForUid(uid);
-        if (packageNames == null) {
-            return false;
-        }
-        for (final String packageName : packageNames) {
-            final PackageInfo packageInfo;
-            try {
-                packageInfo = pm.getPackageInfo(packageName,
-                    PackageManager.GET_PERMISSIONS);
-            } catch (PackageManager.NameNotFoundException e) {
-                // Ignore and try the next package
-                continue;
-            }
-            if (packageInfo.requestedPermissions != null
-                && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param pi the {@link PackageInstaller} object to use
-     * @param originatingUid the UID of the package performing a session based install
-     * @param sessionId ID of the install session
-     * @return {@code true} if the caller is the session owner
-     */
-    public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
-        int sessionId) {
-        if (originatingUid == Process.ROOT_UID) {
-            return true;
-        }
-        PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId);
-        if (sessionInfo == null) {
-            return false;
-        }
-        int installerUid = sessionInfo.getInstallerUid();
-        return originatingUid == installerUid;
-    }
-
-    /**
-     * Generates a stub {@link PackageInfo} object for the given packageName
-     */
-    public 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;
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link SessionInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, SessionInfo info) {
-        PackageManager pm = context.getPackageManager();
-        CharSequence label = info.getAppLabel();
-        Drawable icon = info.getAppIcon() != null ?
-            new BitmapDrawable(context.getResources(), info.getAppIcon())
-            : pm.getDefaultActivityIcon();
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link PackageInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, PackageInfo pkgInfo) {
-        return getAppSnippet(context, pkgInfo.applicationInfo);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link ApplicationInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        CharSequence label = pm.getApplicationLabel(appInfo);
-        Drawable icon = pm.getApplicationIcon(appInfo);
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * supplied APK file
-     */
-    public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo,
-        File sourceFile) {
-        ApplicationInfo appInfoFromFile = processAppInfoForFile(appInfo, sourceFile);
-        CharSequence label = getAppLabelFromFile(context, appInfoFromFile);
-        Drawable icon = getAppIconFromFile(context, appInfoFromFile);
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Utility method to load application label
-     *
-     * @param context context of package that can load the resources
-     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
-     */
-    public static CharSequence getAppLabelFromFile(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        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 (appInfo.labelRes != 0) {
-            try {
-                label = appInfo.loadLabel(pm);
-            } catch (Resources.NotFoundException e) {
-            }
-        }
-        if (label == null) {
-            label = (appInfo.nonLocalizedLabel != null) ?
-                appInfo.nonLocalizedLabel : appInfo.packageName;
-        }
-        return label;
-    }
-
-    /**
-     * Utility method to load application icon
-     *
-     * @param context context of package that can load the resources
-     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
-     */
-    public static Drawable getAppIconFromFile(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        Drawable icon = null;
-        // Try to load the icon from the package's resources. If an app has not explicitly
-        // specified any resource, just use the default icon for now.
-        try {
-            if (appInfo.icon != 0) {
-                try {
-                    icon = appInfo.loadIcon(pm);
-                } catch (Resources.NotFoundException e) {
-                }
-            }
-            if (icon == null) {
-                icon = context.getPackageManager().getDefaultActivityIcon();
-            }
-        } catch (OutOfMemoryError e) {
-            Log.i(TAG, "Could not load app icon", e);
-        }
-        return icon;
-    }
-
-    private static ApplicationInfo processAppInfoForFile(ApplicationInfo appInfo, File sourceFile) {
-        final String archiveFilePath = sourceFile.getAbsolutePath();
-        appInfo.publicSourceDir = archiveFilePath;
-
-        if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
-            final File[] files = sourceFile.getParentFile().listFiles();
-            final String[] splits = Arrays.stream(appInfo.splitNames)
-                .map(i -> findFilePath(files, i + ".apk"))
-                .filter(Objects::nonNull)
-                .toArray(String[]::new);
-
-            appInfo.splitSourceDirs = splits;
-            appInfo.splitPublicSourceDirs = splits;
-        }
-        return appInfo;
-    }
-
-    private static String findFilePath(File[] files, String postfix) {
-        for (File file : files) {
-            final String path = file.getAbsolutePath();
-            if (path.endsWith(postfix)) {
-                return path;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * @return the packageName corresponding to a UID.
-     */
-    public static String getPackageNameForUid(Context context, int sourceUid,
-        String callingPackage) {
-        if (sourceUid == Process.INVALID_UID) {
-            return null;
-        }
-        // If the sourceUid belongs to the system downloads provider, we explicitly return the
-        // name of the Download Manager package. This is because its UID is shared with multiple
-        // packages, resulting in uncertainty about which package will end up first in the list
-        // of packages associated with this UID
-        PackageManager pm = context.getPackageManager();
-        ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo(
-            pm, sourceUid);
-        if (systemDownloadProviderInfo != null) {
-            return systemDownloadProviderInfo.packageName;
-        }
-        String[] packagesForUid = pm.getPackagesForUid(sourceUid);
-        if (packagesForUid == null) {
-            return null;
-        }
-        if (packagesForUid.length > 1) {
-            if (callingPackage != null) {
-                for (String packageName : packagesForUid) {
-                    if (packageName.equals(callingPackage)) {
-                        return packageName;
-                    }
-                }
-            }
-            Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
-        }
-        return packagesForUid[0];
-    }
-
-    /**
-     * Utility method to get package information for a given {@link File}
-     */
-    public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
-        String filePath = sourceFile.getAbsolutePath();
-        if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
-            File dir = sourceFile.getParentFile();
-            if (dir.listFiles().length > 1) {
-                // split apks, use file directory to get archive info
-                filePath = dir.getPath();
-            }
-        }
-        try {
-            return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
-        } catch (Exception ignored) {
-            return null;
-        }
-    }
-
-    /**
-     * Is a profile part of a user?
-     *
-     * @param userManager The user manager
-     * @param userHandle The handle of the user
-     * @param profileHandle The handle of the profile
-     *
-     * @return If the profile is part of the user or the profile parent of the user
-     */
-    public static boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
-        UserHandle profileHandle) {
-        if (userHandle.equals(profileHandle)) {
-            return true;
-        }
-        return userManager.getProfileParent(profileHandle) != null
-            && userManager.getProfileParent(profileHandle).equals(userHandle);
-    }
-
-    /**
-     * The class to hold an incoming package's icon and label.
-     * See {@link #getAppSnippet(Context, SessionInfo)},
-     * {@link #getAppSnippet(Context, PackageInfo)},
-     * {@link #getAppSnippet(Context, ApplicationInfo)},
-     * {@link #getAppSnippet(Context, ApplicationInfo, File)}
-     */
-    public static class AppSnippet {
-
-        private CharSequence mLabel;
-        private Drawable mIcon;
-
-        public AppSnippet(CharSequence label, Drawable icon) {
-            mLabel = label;
-            mIcon = icon;
-        }
-
-        public AppSnippet() {
-        }
-
-        public CharSequence getLabel() {
-            return mLabel;
-        }
-
-        public void setLabel(CharSequence mLabel) {
-            this.mLabel = mLabel;
-        }
-
-        public Drawable getIcon() {
-            return mIcon;
-        }
-
-        public void setIcon(Drawable mIcon) {
-            this.mIcon = mIcon;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
new file mode 100644
index 0000000..8d8c2f1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.model
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import java.io.File
+
+object PackageUtil {
+    private val LOG_TAG = InstallRepository::class.java.simpleName
+    private const val DOWNLOADS_AUTHORITY = "downloads"
+    private const val SPLIT_BASE_APK_END_WITH = "base.apk"
+
+    /**
+     * Determines if the UID belongs to the system downloads provider and returns the
+     * [ApplicationInfo] of the provider
+     *
+     * @param uid UID of the caller
+     * @return [ApplicationInfo] of the provider if a downloads provider exists, it is a
+     * system app, and its UID matches with the passed UID, null otherwise.
+     */
+    private fun getSystemDownloadsProviderInfo(pm: PackageManager, uid: Int): ApplicationInfo? {
+        // Check if there are currently enabled downloads provider on the system.
+        val providerInfo = pm.resolveContentProvider(DOWNLOADS_AUTHORITY, 0)
+            ?: return null
+        val appInfo = providerInfo.applicationInfo
+        return if ((appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) && uid == appInfo.uid) {
+            appInfo
+        } else null
+    }
+
+    /**
+     * Get the maximum target sdk for a UID.
+     *
+     * @param context The context to use
+     * @param uid The UID requesting the install/uninstall
+     * @return The maximum target SDK or -1 if the uid does not match any packages.
+     */
+    @JvmStatic
+    fun getMaxTargetSdkVersionForUid(context: Context, uid: Int): Int {
+        val pm = context.packageManager
+        val packages = pm.getPackagesForUid(uid)
+        var targetSdkVersion = -1
+        if (packages != null) {
+            for (packageName in packages) {
+                try {
+                    val info = pm.getApplicationInfo(packageName!!, 0)
+                    targetSdkVersion = maxOf(targetSdkVersion, info.targetSdkVersion)
+                } catch (e: PackageManager.NameNotFoundException) {
+                    // Ignore and try the next package
+                }
+            }
+        }
+        return targetSdkVersion
+    }
+
+    @JvmStatic
+    fun canPackageQuery(context: Context, callingUid: Int, packageUri: Uri): Boolean {
+        val pm = context.packageManager
+        val info = pm.resolveContentProvider(
+            packageUri.authority!!,
+            PackageManager.ComponentInfoFlags.of(0)
+        ) ?: return false
+        val targetPackage = info.packageName
+        val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
+        for (callingPackage in callingPackages) {
+            try {
+                if (pm.canPackageQuery(callingPackage!!, targetPackage)) {
+                    return true
+                }
+            } catch (e: PackageManager.NameNotFoundException) {
+                // no-op
+            }
+        }
+        return false
+    }
+
+    /**
+     * @param context the [Context] object
+     * @param permission the permission name to check
+     * @param callingUid the UID of the caller who's permission is being checked
+     * @return `true` if the callingUid is granted the said permission
+     */
+    @JvmStatic
+    fun isPermissionGranted(context: Context, permission: String, callingUid: Int): Boolean {
+        return (context.checkPermission(permission, -1, callingUid)
+            == PackageManager.PERMISSION_GRANTED)
+    }
+
+    /**
+     * @param pm the [PackageManager] object
+     * @param permission the permission name to check
+     * @param packageName the name of the package who's permission is being checked
+     * @return `true` if the package is granted the said permission
+     */
+    @JvmStatic
+    fun isPermissionGranted(pm: PackageManager, permission: String, packageName: String): Boolean {
+        return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED
+    }
+
+    /**
+     * @param context the [Context] object
+     * @param callingUid the UID of the caller who's permission is being checked
+     * @param originatingUid the UID from where install is being originated. This could be same as
+     * callingUid or it will be the UID of the package performing a session based install
+     * @param isTrustedSource whether install request is coming from a privileged app or an app that
+     * has [Manifest.permission.INSTALL_PACKAGES] permission granted
+     * @return `true` if the package is granted the said permission
+     */
+    @JvmStatic
+    fun isInstallPermissionGrantedOrRequested(
+        context: Context,
+        callingUid: Int,
+        originatingUid: Int,
+        isTrustedSource: Boolean,
+    ): Boolean {
+        val isDocumentsManager =
+            isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid)
+        val isSystemDownloadsProvider =
+            getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
+
+        if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
+            val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+            if (targetSdkVersion < 0) {
+                // Invalid originating uid supplied. Abort install.
+                Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+                return false
+            } else if (targetSdkVersion >= Build.VERSION_CODES.O
+                && !isUidRequestingPermission(
+                    context.packageManager, originatingUid,
+                    Manifest.permission.REQUEST_INSTALL_PACKAGES
+                )
+            ) {
+                Log.e(
+                    LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+                        + Manifest.permission.REQUEST_INSTALL_PACKAGES
+                )
+                return false
+            }
+        }
+        return true
+    }
+
+    /**
+     * @param pm the [PackageManager] object
+     * @param uid the UID of the caller who's permission is being checked
+     * @param permission the permission name to check
+     * @return `true` if the caller is requesting the said permission in its Manifest
+     */
+    private fun isUidRequestingPermission(
+        pm: PackageManager,
+        uid: Int,
+        permission: String,
+    ): Boolean {
+        val packageNames = pm.getPackagesForUid(uid) ?: return false
+        for (packageName in packageNames) {
+            val packageInfo: PackageInfo = try {
+                pm.getPackageInfo(packageName!!, PackageManager.GET_PERMISSIONS)
+            } catch (e: PackageManager.NameNotFoundException) {
+                // Ignore and try the next package
+                continue
+            }
+            if (packageInfo.requestedPermissions != null
+                && listOf(*packageInfo.requestedPermissions!!).contains(permission)
+            ) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * @param pi the [PackageInstaller] object to use
+     * @param originatingUid the UID of the package performing a session based install
+     * @param sessionId ID of the install session
+     * @return `true` if the caller is the session owner
+     */
+    @JvmStatic
+    fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
+        if (originatingUid == Process.ROOT_UID) {
+            return true
+        }
+        val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
+        val installerUid = sessionInfo.getInstallerUid()
+        return originatingUid == installerUid
+    }
+
+    /**
+     * Generates a stub [PackageInfo] object for the given packageName
+     */
+    @JvmStatic
+    fun generateStubPackageInfo(packageName: String?): PackageInfo {
+        val info = PackageInfo()
+        val aInfo = ApplicationInfo()
+        info.applicationInfo = aInfo
+        info.applicationInfo!!.packageName = packageName
+        info.packageName = info.applicationInfo!!.packageName
+        return info
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [PackageInstaller.SessionInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, info: PackageInstaller.SessionInfo): AppSnippet {
+        val pm = context.packageManager
+        val label = info.getAppLabel()
+        val icon = if (info.getAppIcon() != null) BitmapDrawable(
+            context.resources,
+            info.getAppIcon()
+        ) else pm.defaultActivityIcon
+        return AppSnippet(label, icon)
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [PackageInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, pkgInfo: PackageInfo): AppSnippet {
+        return pkgInfo.applicationInfo?.let { getAppSnippet(context, it) } ?: run {
+            AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+        }
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [ApplicationInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, appInfo: ApplicationInfo): AppSnippet {
+        val pm = context.packageManager
+        val label = pm.getApplicationLabel(appInfo)
+        val icon = pm.getApplicationIcon(appInfo)
+        return AppSnippet(label, icon)
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * supplied APK file
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, pkgInfo: PackageInfo, sourceFile: File): AppSnippet {
+        pkgInfo.applicationInfo?.let {
+            val appInfoFromFile = processAppInfoForFile(it, sourceFile)
+            val label = getAppLabelFromFile(context, appInfoFromFile)
+            val icon = getAppIconFromFile(context, appInfoFromFile)
+            return AppSnippet(label, icon)
+        } ?: run {
+            return AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+        }
+    }
+
+    /**
+     * Utility method to load application label
+     *
+     * @param context context of package that can load the resources
+     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+     */
+    private fun getAppLabelFromFile(context: Context, appInfo: ApplicationInfo): CharSequence? {
+        val pm = context.packageManager
+        var label: CharSequence? = 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 (appInfo.labelRes != 0) {
+            try {
+                label = appInfo.loadLabel(pm)
+            } catch (e: Resources.NotFoundException) {
+            }
+        }
+        if (label == null) {
+            label = if (appInfo.nonLocalizedLabel != null) appInfo.nonLocalizedLabel
+            else appInfo.packageName
+        }
+        return label
+    }
+
+    /**
+     * Utility method to load application icon
+     *
+     * @param context context of package that can load the resources
+     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+     */
+    private fun getAppIconFromFile(context: Context, appInfo: ApplicationInfo): Drawable? {
+        val pm = context.packageManager
+        var icon: Drawable? = null
+        // Try to load the icon from the package's resources. If an app has not explicitly
+        // specified any resource, just use the default icon for now.
+        try {
+            if (appInfo.icon != 0) {
+                try {
+                    icon = appInfo.loadIcon(pm)
+                } catch (e: Resources.NotFoundException) {
+                }
+            }
+            if (icon == null) {
+                icon = context.packageManager.defaultActivityIcon
+            }
+        } catch (e: OutOfMemoryError) {
+            Log.i(LOG_TAG, "Could not load app icon", e)
+        }
+        return icon
+    }
+
+    private fun processAppInfoForFile(appInfo: ApplicationInfo, sourceFile: File): ApplicationInfo {
+        val archiveFilePath = sourceFile.absolutePath
+        appInfo.publicSourceDir = archiveFilePath
+        if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
+            val files = sourceFile.parentFile?.listFiles()
+            val splits = appInfo.splitNames!!
+                .mapNotNull { findFilePath(files, "$it.apk") }
+                .toTypedArray()
+
+            appInfo.splitSourceDirs = splits
+            appInfo.splitPublicSourceDirs = splits
+        }
+        return appInfo
+    }
+
+    private fun findFilePath(files: Array<File>?, postfix: String): String? {
+        files?.let {
+            for (file in it) {
+                val path = file.absolutePath
+                if (path.endsWith(postfix)) {
+                    return path
+                }
+            }
+        }
+        return null
+    }
+
+    /**
+     * @return the packageName corresponding to a UID.
+     */
+    @JvmStatic
+    fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
+        if (sourceUid == Process.INVALID_UID) {
+            return null
+        }
+        // If the sourceUid belongs to the system downloads provider, we explicitly return the
+        // name of the Download Manager package. This is because its UID is shared with multiple
+        // packages, resulting in uncertainty about which package will end up first in the list
+        // of packages associated with this UID
+        val pm = context.packageManager
+        val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+        if (systemDownloadProviderInfo != null) {
+            return systemDownloadProviderInfo.packageName
+        }
+        val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+        if (packagesForUid.size > 1) {
+            if (callingPackage != null) {
+                for (packageName in packagesForUid) {
+                    if (packageName == callingPackage) {
+                        return packageName
+                    }
+                }
+            }
+            Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
+        }
+        return packagesForUid[0]
+    }
+
+    /**
+     * Utility method to get package information for a given [File]
+     */
+    @JvmStatic
+    fun getPackageInfo(context: Context, sourceFile: File, flags: Int): PackageInfo? {
+        var filePath = sourceFile.absolutePath
+        if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
+            val dir = sourceFile.parentFile
+            if ((dir?.listFiles()?.size ?: 0) > 1) {
+                // split apks, use file directory to get archive info
+                filePath = dir.path
+            }
+        }
+        return try {
+            context.packageManager.getPackageArchiveInfo(filePath, flags)
+        } catch (ignored: Exception) {
+            null
+        }
+    }
+
+    /**
+     * Is a profile part of a user?
+     *
+     * @param userManager The user manager
+     * @param userHandle The handle of the user
+     * @param profileHandle The handle of the profile
+     *
+     * @return If the profile is part of the user or the profile parent of the user
+     */
+    @JvmStatic
+    fun isProfileOfOrSame(
+        userManager: UserManager,
+        userHandle: UserHandle,
+        profileHandle: UserHandle?,
+    ): Boolean {
+        if (profileHandle == null) {
+            return false
+        }
+        return if (userHandle == profileHandle) {
+            true
+        } else userManager.getProfileParent(profileHandle) != null
+            && userManager.getProfileParent(profileHandle) == userHandle
+    }
+
+    /**
+     * The class to hold an incoming package's icon and label.
+     * See [getAppSnippet]
+     */
+    data class AppSnippet(var label: CharSequence?, var icon: Drawable?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
deleted file mode 100644
index a2c81f1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {
-
-    private static final String TAG = SessionStager.class.getSimpleName();
-    private final Context mContext;
-    private final Uri mUri;
-    private final int mStagedSessionId;
-    private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
-    private final SessionStageListener mListener;
-
-    SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
-        mContext = context;
-        mUri = uri;
-        mStagedSessionId = stagedSessionId;
-        mListener = listener;
-    }
-
-    @Override
-    protected PackageInstaller.SessionInfo doInBackground(Void... params) {
-        PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
-        try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
-            InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
-            session.setStagingProgress(0);
-
-            if (in == null) {
-                return null;
-            }
-            final long sizeBytes = getContentSizeBytes();
-            mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);
-
-            long totalRead = 0;
-            try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
-                byte[] buffer = new byte[1024 * 1024];
-                while (true) {
-                    int numRead = in.read(buffer);
-
-                    if (numRead == -1) {
-                        session.fsync(out);
-                        break;
-                    }
-
-                    if (isCancelled()) {
-                        break;
-                    }
-
-                    out.write(buffer, 0, numRead);
-                    if (sizeBytes > 0) {
-                        totalRead += numRead;
-                        float fraction = ((float) totalRead / (float) sizeBytes);
-                        session.setStagingProgress(fraction);
-                        publishProgress((int) (fraction * 100.0));
-                    }
-                }
-            }
-            return pi.getSessionInfo(mStagedSessionId);
-        } catch (IOException | SecurityException | IllegalStateException
-                 | IllegalArgumentException e) {
-            Log.w(TAG, "Error staging apk from content URI", e);
-            return null;
-        }
-    }
-
-    private long getContentSizeBytes() {
-        try (AssetFileDescriptor afd = mContext.getContentResolver()
-            .openAssetFileDescriptor(mUri, "r")) {
-            return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to open asset file descriptor", e);
-            return UNKNOWN_LENGTH;
-        }
-    }
-
-    public MutableLiveData<Integer> getProgress() {
-        return mProgressLiveData;
-    }
-
-    @Override
-    protected void onProgressUpdate(Integer... progress) {
-        if (progress != null && progress.length > 0) {
-            mProgressLiveData.setValue(progress[0]);
-        }
-    }
-
-    @Override
-    protected void onPostExecute(SessionInfo sessionInfo) {
-        if (sessionInfo == null || !sessionInfo.isActive()
-            || sessionInfo.getResolvedBaseApkPath() == null) {
-            Log.w(TAG, "Session info is invalid: " + sessionInfo);
-            mListener.onStagingFailure();
-            return;
-        }
-        mListener.onStagingSuccess(sessionInfo);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
new file mode 100644
index 0000000..c9bfa17
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.model
+
+import android.content.Context
+import android.content.pm.PackageInstaller
+import android.content.res.AssetFileDescriptor
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class SessionStager internal constructor(
+    private val context: Context,
+    private val uri: Uri,
+    private val stagedSessionId: Int
+) {
+
+    companion object {
+        private val LOG_TAG = SessionStager::class.java.simpleName
+    }
+
+    private val _progress = MutableLiveData(0)
+    val progress: LiveData<Int>
+        get() = _progress
+
+    suspend fun execute(): Boolean = withContext(Dispatchers.IO) {
+        val pi: PackageInstaller = context.packageManager.packageInstaller
+        var sessionInfo: PackageInstaller.SessionInfo?
+        try {
+            val session = pi.openSession(stagedSessionId)
+            context.contentResolver.openInputStream(uri).use { instream ->
+                session.setStagingProgress(0f)
+
+                if (instream == null) {
+                    return@withContext false
+                }
+
+                val sizeBytes = getContentSizeBytes()
+                publishProgress(if (sizeBytes > 0) 0 else -1)
+
+                var totalRead: Long = 0
+                session.openWrite("PackageInstaller", 0, sizeBytes).use { out ->
+                    val buffer = ByteArray(1024 * 1024)
+                    while (true) {
+                        val numRead = instream.read(buffer)
+                        if (numRead == -1) {
+                            session.fsync(out)
+                            break
+                        }
+                        out.write(buffer, 0, numRead)
+
+                        if (sizeBytes > 0) {
+                            totalRead += numRead.toLong()
+                            val fraction = totalRead.toFloat() / sizeBytes.toFloat()
+                            session.setStagingProgress(fraction)
+                            publishProgress((fraction * 100.0).toInt())
+                        }
+                    }
+                }
+                sessionInfo = pi.getSessionInfo(stagedSessionId)
+            }
+        } catch (e: Exception) {
+            Log.w(LOG_TAG, "Error staging apk from content URI", e)
+            sessionInfo = null
+        }
+
+        return@withContext if (sessionInfo == null
+            || !sessionInfo?.isActive!!
+            || sessionInfo?.resolvedBaseApkPath == null
+        ) {
+            Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
+            false
+        } else {
+            true
+        }
+    }
+
+    private fun getContentSizeBytes(): Long {
+        return try {
+            context.contentResolver
+                .openAssetFileDescriptor(uri, "r")
+                .use { afd -> afd?.length ?: AssetFileDescriptor.UNKNOWN_LENGTH }
+        } catch (e: IOException) {
+            Log.w(LOG_TAG, "Failed to open asset file descriptor", e)
+            AssetFileDescriptor.UNKNOWN_LENGTH
+        }
+    }
+
+    private suspend fun publishProgress(progressValue: Int) = withContext(Dispatchers.Main) {
+        _progress.value = progressValue
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
deleted file mode 100644
index a07c532..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
-import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.usage.StorageStats;
-import android.app.usage.StorageStatsManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.UninstallCompleteCallback;
-import android.content.pm.VersionedPackage;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import java.io.IOException;
-import java.util.List;
-
-public class UninstallRepository {
-
-    private static final String TAG = UninstallRepository.class.getSimpleName();
-    private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall_failure";
-    private static final String BROADCAST_ACTION =
-        "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
-    private static final String EXTRA_UNINSTALL_ID =
-        "com.android.packageinstaller.extra.UNINSTALL_ID";
-    private static final String EXTRA_APP_LABEL =
-        "com.android.packageinstaller.extra.APP_LABEL";
-    private static final String EXTRA_IS_CLONE_APP =
-        "com.android.packageinstaller.extra.IS_CLONE_APP";
-    private static final String EXTRA_PACKAGE_NAME =
-        "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME";
-
-    private final Context mContext;
-    private final AppOpsManager mAppOpsManager;
-    private final PackageManager mPackageManager;
-    private final UserManager mUserManager;
-    private final NotificationManager mNotificationManager;
-    private final MutableLiveData<UninstallStage> mUninstallResult = new MutableLiveData<>();
-    public UserHandle mUninstalledUser;
-    public UninstallCompleteCallback mCallback;
-    private ApplicationInfo mTargetAppInfo;
-    private ActivityInfo mTargetActivityInfo;
-    private Intent mIntent;
-    private CharSequence mTargetAppLabel;
-    private String mTargetPackageName;
-    private String mCallingActivity;
-    private boolean mUninstallFromAllUsers;
-    private boolean mIsClonedApp;
-    private int mUninstallId;
-
-    public UninstallRepository(Context context) {
-        mContext = context;
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
-        mPackageManager = context.getPackageManager();
-        mUserManager = context.getSystemService(UserManager.class);
-        mNotificationManager = context.getSystemService(NotificationManager.class);
-    }
-
-    public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) {
-        mIntent = intent;
-
-        int callingUid = callerInfo.getUid();
-        mCallingActivity = callerInfo.getActivityName();
-
-        if (callingUid == Process.INVALID_UID) {
-            Log.e(TAG, "Could not determine the launching uid.");
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-            // TODO: should we give any indication to the user?
-        }
-
-        String callingPackage = getPackageNameForUid(mContext, callingUid, null);
-        if (callingPackage == null) {
-            Log.e(TAG, "Package not found for originating uid " + callingUid);
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-        } else {
-            if (mAppOpsManager.noteOpNoThrow(
-                AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
-                != MODE_ALLOWED) {
-                Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
-                return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-            }
-        }
-
-        if (getMaxTargetSdkVersionForUid(mContext, callingUid) >= Build.VERSION_CODES.P
-            && !isPermissionGranted(mContext, Manifest.permission.REQUEST_DELETE_PACKAGES,
-            callingUid)
-            && !isPermissionGranted(mContext, Manifest.permission.DELETE_PACKAGES, callingUid)) {
-            Log.e(TAG, "Uid " + callingUid + " does not have "
-                + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
-                + Manifest.permission.DELETE_PACKAGES);
-
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-        }
-
-        // Get intent information.
-        // We expect an intent with URI of the form package:<packageName>#<className>
-        // className is optional; if specified, it is the activity the user chose to uninstall
-        final Uri packageUri = intent.getData();
-        if (packageUri == null) {
-            Log.e(TAG, "No package URI in intent");
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-        mTargetPackageName = packageUri.getEncodedSchemeSpecificPart();
-        if (mTargetPackageName == null) {
-            Log.e(TAG, "Invalid package name in URI: " + packageUri);
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-
-        mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
-            false);
-        if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) {
-            Log.e(TAG, "Only admin user can request uninstall for all users");
-            return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
-        }
-
-        mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
-        if (mUninstalledUser == null) {
-            mUninstalledUser = Process.myUserHandle();
-        } else {
-            List<UserHandle> profiles = mUserManager.getUserProfiles();
-            if (!profiles.contains(mUninstalledUser)) {
-                Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
-                    + "for user " + mUninstalledUser);
-                return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
-            }
-        }
-
-        mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
-            PackageManager.UninstallCompleteCallback.class);
-
-        try {
-            mTargetAppInfo = mPackageManager.getApplicationInfo(mTargetPackageName,
-                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Unable to get packageName");
-        }
-
-        if (mTargetAppInfo == null) {
-            Log.e(TAG, "Invalid packageName: " + mTargetPackageName);
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-
-        // The class name may have been specified (e.g. when deleting an app from all apps)
-        final String className = packageUri.getFragment();
-        if (className != null) {
-            try {
-                mTargetActivityInfo = mPackageManager.getActivityInfo(
-                    new ComponentName(mTargetPackageName, className),
-                    PackageManager.ComponentInfoFlags.of(0));
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.e(TAG, "Unable to get className");
-                // Continue as the ActivityInfo isn't critical.
-            }
-        }
-
-        return new UninstallReady();
-    }
-
-    public UninstallStage generateUninstallDetails() {
-        UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder();
-        StringBuilder messageBuilder = new StringBuilder();
-
-        mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager);
-
-        // If the Activity label differs from the App label, then make sure the user
-        // knows the Activity belongs to the App being uninstalled.
-        if (mTargetActivityInfo != null) {
-            final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager);
-            if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) {
-                messageBuilder.append(
-                    mContext.getString(R.string.uninstall_activity_text, activityLabel));
-                messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n");
-            }
-        }
-
-        final boolean isUpdate =
-            (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-        final UserHandle myUserHandle = Process.myUserHandle();
-        boolean isSingleUser = isSingleUser();
-
-        if (isUpdate) {
-            messageBuilder.append(mContext.getString(
-                isSingleUser ? R.string.uninstall_update_text :
-                    R.string.uninstall_update_text_multiuser));
-        } else if (mUninstallFromAllUsers && !isSingleUser) {
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_all_users));
-        } else if (!mUninstalledUser.equals(myUserHandle)) {
-            // Uninstalling user is issuing uninstall for another user
-            UserManager customUserManager = mContext.createContextAsUser(mUninstalledUser, 0)
-                .getSystemService(UserManager.class);
-            String userName = customUserManager.getUserName();
-
-            String uninstalledUserType = getUninstalledUserType(myUserHandle, mUninstalledUser);
-            String messageString;
-            if (USER_TYPE_PROFILE_MANAGED.equals(uninstalledUserType)) {
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_current_user_work_profile, userName);
-            } else if (USER_TYPE_PROFILE_CLONE.equals(uninstalledUserType)) {
-                mIsClonedApp = true;
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_current_user_clone_profile);
-            } else {
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_user, userName);
-            }
-            messageBuilder.append(messageString);
-        } else if (isCloneProfile(mUninstalledUser)) {
-            mIsClonedApp = true;
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_current_user_clone_profile));
-        } else if (myUserHandle.equals(UserHandle.SYSTEM)
-            && hasClonedInstance(mTargetAppInfo.packageName)) {
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel));
-        } else {
-            messageBuilder.append(mContext.getString(R.string.uninstall_application_text));
-        }
-
-        uarBuilder.setMessage(messageBuilder.toString());
-
-        if (mIsClonedApp) {
-            uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel));
-        } else {
-            uarBuilder.setTitle(mTargetAppLabel.toString());
-        }
-
-        boolean suggestToKeepAppData = false;
-        try {
-            PackageInfo pkgInfo = mPackageManager.getPackageInfo(mTargetPackageName, 0);
-            suggestToKeepAppData =
-                pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.hasFragileUserData();
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Cannot check hasFragileUserData for " + mTargetPackageName, e);
-        }
-
-        long appDataSize = 0;
-        if (suggestToKeepAppData) {
-            appDataSize = getAppDataSize(mTargetPackageName,
-                mUninstallFromAllUsers ? null : mUninstalledUser);
-        }
-        uarBuilder.setAppDataSize(appDataSize);
-
-        return uarBuilder.build();
-    }
-
-    /**
-     * Returns whether there is only one "full" user on this device.
-     *
-     * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
-     * headless system user mode}, the system user is not "full", so it's not be considered in the
-     * calculation.</p>
-     */
-    private boolean isSingleUser() {
-        final int userCount = mUserManager.getUserCount();
-        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
-    }
-
-    /**
-     * Returns the type of the user from where an app is being uninstalled. We are concerned with
-     * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
-     * belong to the same profile group.
-     */
-    @Nullable
-    private String getUninstalledUserType(UserHandle myUserHandle,
-        UserHandle uninstalledUserHandle) {
-        if (!mUserManager.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
-            return null;
-        }
-
-        UserManager customUserManager = mContext.createContextAsUser(uninstalledUserHandle, 0)
-            .getSystemService(UserManager.class);
-        String[] userTypes = {USER_TYPE_PROFILE_MANAGED, USER_TYPE_PROFILE_CLONE};
-        for (String userType : userTypes) {
-            if (customUserManager.isUserOfType(userType)) {
-                return userType;
-            }
-        }
-        return null;
-    }
-
-    private boolean hasClonedInstance(String packageName) {
-        // Check if clone user is present on the device.
-        UserHandle cloneUser = null;
-        List<UserHandle> profiles = mUserManager.getUserProfiles();
-        for (UserHandle userHandle : profiles) {
-            if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
-                cloneUser = userHandle;
-                break;
-            }
-        }
-        // Check if another instance of given package exists in clone user profile.
-        try {
-            return cloneUser != null
-                && mPackageManager.getPackageUidAsUser(packageName,
-                PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    private boolean isCloneProfile(UserHandle userHandle) {
-        UserManager customUserManager = mContext.createContextAsUser(userHandle, 0)
-            .getSystemService(UserManager.class);
-        return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE);
-    }
-
-    /**
-     * Get number of bytes of the app data of the package.
-     *
-     * @param pkg The package that might have app data.
-     * @param user The user the package belongs to or {@code null} if files of all users should
-     *     be counted.
-     * @return The number of bytes.
-     */
-    private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
-        if (user != null) {
-            return getAppDataSizeForUser(pkg, user);
-        }
-        // We are uninstalling from all users. Get cumulative app data size for all users.
-        List<UserHandle> userHandles = mUserManager.getUserHandles(true);
-        long totalAppDataSize = 0;
-        int numUsers = userHandles.size();
-        for (int i = 0; i < numUsers; i++) {
-            totalAppDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
-        }
-        return totalAppDataSize;
-    }
-
-    /**
-     * Get number of bytes of the app data of the package.
-     *
-     * @param pkg The package that might have app data.
-     * @param user The user the package belongs to
-     * @return The number of bytes.
-     */
-    private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
-        StorageStatsManager storageStatsManager =
-            mContext.getSystemService(StorageStatsManager.class);
-        try {
-            StorageStats stats = storageStatsManager.queryStatsForPackage(
-                mPackageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user);
-            return stats.getDataBytes();
-        } catch (PackageManager.NameNotFoundException | IOException | SecurityException e) {
-            Log.e(TAG, "Cannot determine amount of app data for " + pkg, e);
-        }
-        return 0;
-    }
-
-    public void initiateUninstall(boolean keepData) {
-        // Get an uninstallId to track results and show a notification on non-TV devices.
-        try {
-            mUninstallId = UninstallEventReceiver.addObserver(mContext,
-                EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult);
-        } catch (EventResultPersister.OutOfIdsException e) {
-            Log.e(TAG, "Failed to start uninstall", e);
-            handleUninstallResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
-            return;
-        }
-
-        // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
-        mUninstallResult.setValue(new UninstallUninstalling(mTargetAppLabel, mIsClonedApp));
-
-        Bundle uninstallData = new Bundle();
-        uninstallData.putInt(EXTRA_UNINSTALL_ID, mUninstallId);
-        uninstallData.putString(EXTRA_PACKAGE_NAME, mTargetPackageName);
-        uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, mUninstallFromAllUsers);
-        uninstallData.putCharSequence(EXTRA_APP_LABEL, mTargetAppLabel);
-        uninstallData.putBoolean(EXTRA_IS_CLONE_APP, mIsClonedApp);
-        Log.i(TAG, "Uninstalling extras = " + uninstallData);
-
-        // Get a PendingIntent for result broadcast and issue an uninstall request
-        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
-        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
-        broadcastIntent.setPackage(mContext.getPackageName());
-
-        PendingIntent pendingIntent =
-            PendingIntent.getBroadcast(mContext, mUninstallId, broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
-        if (!startUninstall(mTargetPackageName, mUninstalledUser, pendingIntent,
-            mUninstallFromAllUsers, keepData)) {
-            handleUninstallResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
-        }
-    }
-
-    private void handleUninstallResult(int status, int legacyStatus, @Nullable String message,
-        int serviceId) {
-        if (mCallback != null) {
-            // The caller will be informed about the result via a callback
-            mCallback.onUninstallComplete(mTargetPackageName, legacyStatus, message);
-
-            // Since the caller already received the results, just finish the app at this point
-            mUninstallResult.setValue(null);
-            return;
-        }
-
-        boolean returnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-        if (returnResult || mCallingActivity != null) {
-            Intent intent = new Intent();
-            intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
-
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
-                    .setResultIntent(intent)
-                    .setActivityResultCode(Activity.RESULT_OK);
-                mUninstallResult.setValue(successBuilder.build());
-            } else {
-                UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(true)
-                    .setResultIntent(intent)
-                    .setActivityResultCode(Activity.RESULT_FIRST_USER);
-                mUninstallResult.setValue(failedBuilder.build());
-            }
-            return;
-        }
-
-        // Caller did not want the result back. So, we either show a Toast, or a Notification.
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
-                .setActivityResultCode(legacyStatus)
-                .setMessage(mIsClonedApp
-                    ? mContext.getString(R.string.uninstall_done_clone_app, mTargetAppLabel)
-                    : mContext.getString(R.string.uninstall_done_app, mTargetAppLabel));
-            mUninstallResult.setValue(successBuilder.build());
-        } else {
-            UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(false);
-            Notification.Builder uninstallFailedNotification = null;
-
-            NotificationChannel uninstallFailureChannel = new NotificationChannel(
-                UNINSTALL_FAILURE_CHANNEL,
-                mContext.getString(R.string.uninstall_failure_notification_channel),
-                NotificationManager.IMPORTANCE_DEFAULT);
-            mNotificationManager.createNotificationChannel(uninstallFailureChannel);
-
-            uninstallFailedNotification = new Notification.Builder(mContext,
-                UNINSTALL_FAILURE_CHANNEL);
-
-            UserHandle myUserHandle = Process.myUserHandle();
-            switch (legacyStatus) {
-                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
-                    // Find out if the package is an active admin for some non-current user.
-                    UserHandle otherBlockingUserHandle =
-                        findUserOfDeviceAdmin(myUserHandle, mTargetPackageName);
-
-                    if (otherBlockingUserHandle == null) {
-                        Log.d(TAG, "Uninstall failed because " + mTargetPackageName
-                            + " is a device admin");
-
-                        addDeviceManagerButton(mContext, uninstallFailedNotification);
-                        setBigText(uninstallFailedNotification, mContext.getString(
-                            R.string.uninstall_failed_device_policy_manager));
-                    } else {
-                        Log.d(TAG, "Uninstall failed because " + mTargetPackageName
-                            + " is a device admin of user " + otherBlockingUserHandle);
-
-                        String userName =
-                            mContext.createContextAsUser(otherBlockingUserHandle, 0)
-                                .getSystemService(UserManager.class).getUserName();
-                        setBigText(uninstallFailedNotification, String.format(
-                            mContext.getString(
-                                R.string.uninstall_failed_device_policy_manager_of_user),
-                            userName));
-                    }
-                }
-                case PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
-                    UserHandle otherBlockingUserHandle = findBlockingUser(mTargetPackageName);
-                    boolean isProfileOfOrSame = isProfileOfOrSame(mUserManager, myUserHandle,
-                        otherBlockingUserHandle);
-
-                    if (isProfileOfOrSame) {
-                        addDeviceManagerButton(mContext, uninstallFailedNotification);
-                    } else {
-                        addManageUsersButton(mContext, uninstallFailedNotification);
-                    }
-
-                    String bigText = null;
-                    if (otherBlockingUserHandle == null) {
-                        Log.d(TAG, "Uninstall failed for " + mTargetPackageName +
-                            " with code " + status + " no blocking user");
-                    } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
-                        bigText = mContext.getString(
-                            R.string.uninstall_blocked_device_owner);
-                    } else {
-                        bigText = mContext.getString(mUninstallFromAllUsers ?
-                            R.string.uninstall_all_blocked_profile_owner
-                            : R.string.uninstall_blocked_profile_owner);
-                    }
-                    if (bigText != null) {
-                        setBigText(uninstallFailedNotification, bigText);
-                    }
-                }
-                default -> {
-                    Log.d(TAG, "Uninstall blocked for " + mTargetPackageName
-                        + " with legacy code " + legacyStatus);
-                }
-            }
-
-            uninstallFailedNotification.setContentTitle(
-                mContext.getString(R.string.uninstall_failed_app, mTargetAppLabel));
-            uninstallFailedNotification.setOngoing(false);
-            uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
-            failedBuilder.setUninstallNotification(mUninstallId,
-                uninstallFailedNotification.build());
-
-            mUninstallResult.setValue(failedBuilder.build());
-        }
-    }
-
-    /**
-     * @param myUserHandle {@link UserHandle} of the current user.
-     * @param packageName Name of the package being uninstalled.
-     * @return the {@link UserHandle} of the user in which a package is a device admin.
-     */
-    @Nullable
-    private UserHandle findUserOfDeviceAdmin(UserHandle myUserHandle, String packageName) {
-        for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
-            // We only catch the case when the user in question is neither the
-            // current user nor its profile.
-            if (isProfileOfOrSame(mUserManager, myUserHandle, otherUserHandle)) {
-                continue;
-            }
-            DevicePolicyManager dpm = mContext.createContextAsUser(otherUserHandle, 0)
-                    .getSystemService(DevicePolicyManager.class);
-            if (dpm.packageHasActiveAdmins(packageName)) {
-                return otherUserHandle;
-            }
-        }
-        return null;
-    }
-
-    /**
-     *
-     * @param packageName Name of the package being uninstalled.
-     * @return {@link UserHandle} of the user in which a package is blocked from being uninstalled.
-     */
-    @Nullable
-    private UserHandle findBlockingUser(String packageName) {
-        for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
-            // TODO (b/307399586): Add a negation when the logic of the method
-            //  is fixed
-            if (mPackageManager.canUserUninstall(packageName, otherUserHandle)) {
-                return otherUserHandle;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Set big text for the notification.
-     *
-     * @param builder The builder of the notification
-     * @param text The text to set.
-     */
-    private void setBigText(@NonNull Notification.Builder builder,
-        @NonNull CharSequence text) {
-        builder.setStyle(new Notification.BigTextStyle().bigText(text));
-    }
-
-    /**
-     * Add a button to the notification that links to the user management.
-     *
-     * @param context The context the notification is created in
-     * @param builder The builder of the notification
-     */
-    private void addManageUsersButton(@NonNull Context context,
-        @NonNull Notification.Builder builder) {
-        builder.addAction((new Notification.Action.Builder(
-            Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
-            context.getString(R.string.manage_users),
-            PendingIntent.getActivity(context, 0, getUserSettingsIntent(),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
-    }
-
-    private Intent getUserSettingsIntent() {
-        Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    /**
-     * Add a button to the notification that links to the device policy management.
-     *
-     * @param context The context the notification is created in
-     * @param builder The builder of the notification
-     */
-    private void addDeviceManagerButton(@NonNull Context context,
-        @NonNull Notification.Builder builder) {
-        builder.addAction((new Notification.Action.Builder(
-            Icon.createWithResource(context, R.drawable.ic_lock),
-            context.getString(R.string.manage_device_administrators),
-            PendingIntent.getActivity(context, 0, getDeviceManagerIntent(),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
-    }
-
-    private Intent getDeviceManagerIntent() {
-        Intent intent = new Intent();
-        intent.setClassName("com.android.settings",
-            "com.android.settings.Settings$DeviceAdminSettingsActivity");
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    /**
-     * Starts an uninstall for the given package.
-     *
-     * @return {@code true} if there was no exception while uninstalling. This does not represent
-     *     the result of the uninstall. Result will be made available in
-     *     {@link #handleUninstallResult(int, int, String, int)}
-     */
-    private boolean startUninstall(String packageName, UserHandle targetUser,
-        PendingIntent pendingIntent, boolean uninstallFromAllUsers, boolean keepData) {
-        int flags = uninstallFromAllUsers ? PackageManager.DELETE_ALL_USERS : 0;
-        flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
-        try {
-            mContext.createContextAsUser(targetUser, 0)
-                .getPackageManager().getPackageInstaller().uninstall(
-                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
-                    flags, pendingIntent.getIntentSender());
-            return true;
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Failed to uninstall", e);
-            return false;
-        }
-    }
-
-    public void cancelInstall() {
-        if (mCallback != null) {
-            mCallback.onUninstallComplete(mTargetPackageName,
-                PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
-        }
-    }
-
-    public MutableLiveData<UninstallStage> getUninstallResult() {
-        return mUninstallResult;
-    }
-
-    public static class CallerInfo {
-
-        private final String mActivityName;
-        private final int mUid;
-
-        public CallerInfo(String activityName, int uid) {
-            mActivityName = activityName;
-            mUid = uid;
-        }
-
-        public String getActivityName() {
-            return mActivityName;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
new file mode 100644
index 0000000..7cc95c5
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.pm.VersionedPackage
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.UninstallEventReceiver
+import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
+
+class UninstallRepository(private val context: Context) {
+
+    private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+    private val packageManager: PackageManager = context.packageManager
+    private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+    private val notificationManager: NotificationManager? =
+        context.getSystemService(NotificationManager::class.java)
+    val uninstallResult = MutableLiveData<UninstallStage?>()
+    private var uninstalledUser: UserHandle? = null
+    private var callback: PackageManager.UninstallCompleteCallback? = null
+    private var targetAppInfo: ApplicationInfo? = null
+    private var targetActivityInfo: ActivityInfo? = null
+    private lateinit var intent: Intent
+    private lateinit var targetAppLabel: CharSequence
+    private var targetPackageName: String? = null
+    private var callingActivity: String? = null
+    private var uninstallFromAllUsers = false
+    private var isClonedApp = false
+    private var uninstallId = 0
+
+    fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
+        this.intent = intent
+
+        val callingUid = callerInfo.uid
+        callingActivity = callerInfo.activityName
+
+        if (callingUid == Process.INVALID_UID) {
+            Log.e(LOG_TAG, "Could not determine the launching uid.")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+            // TODO: should we give any indication to the user?
+        }
+
+        val callingPackage = getPackageNameForUid(context, callingUid, null)
+        if (callingPackage == null) {
+            Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+        } else {
+            if (appOpsManager!!.noteOpNoThrow(
+                    AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage
+                ) != AppOpsManager.MODE_ALLOWED
+            ) {
+                Log.e(LOG_TAG, "Install from uid $callingUid disallowed by AppOps")
+                return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+            }
+        }
+
+        if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P
+            && !isPermissionGranted(
+                context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
+            )
+            && !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
+        ) {
+            Log.e(
+                LOG_TAG, "Uid " + callingUid + " does not have "
+                    + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+                    + Manifest.permission.DELETE_PACKAGES
+            )
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+        }
+
+        // Get intent information.
+        // We expect an intent with URI of the form package:<packageName>#<className>
+        // className is optional; if specified, it is the activity the user chose to uninstall
+        val packageUri = intent.data
+        if (packageUri == null) {
+            Log.e(LOG_TAG, "No package URI in intent")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+        targetPackageName = packageUri.encodedSchemeSpecificPart
+        if (targetPackageName == null) {
+            Log.e(LOG_TAG, "Invalid package name in URI: $packageUri")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+
+        uninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)
+        if (uninstallFromAllUsers && !userManager!!.isAdminUser) {
+            Log.e(LOG_TAG, "Only admin user can request uninstall for all users")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+        }
+
+        uninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
+        if (uninstalledUser == null) {
+            uninstalledUser = Process.myUserHandle()
+        } else {
+            val profiles = userManager!!.userProfiles
+            if (!profiles.contains(uninstalledUser)) {
+                Log.e(
+                    LOG_TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+                        + "for user " + uninstalledUser
+                )
+                return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+            }
+        }
+
+        callback = intent.getParcelableExtra(
+            PackageInstaller.EXTRA_CALLBACK, PackageManager.UninstallCompleteCallback::class.java
+        )
+
+        try {
+            targetAppInfo = packageManager.getApplicationInfo(
+                targetPackageName!!,
+                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
+            )
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(LOG_TAG, "Unable to get packageName")
+        }
+
+        if (targetAppInfo == null) {
+            Log.e(LOG_TAG, "Invalid packageName: $targetPackageName")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+
+        // The class name may have been specified (e.g. when deleting an app from all apps)
+        val className = packageUri.fragment
+        if (className != null) {
+            try {
+                targetActivityInfo = packageManager.getActivityInfo(
+                    ComponentName(targetPackageName!!, className),
+                    PackageManager.ComponentInfoFlags.of(0)
+                )
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(LOG_TAG, "Unable to get className")
+                // Continue as the ActivityInfo isn't critical.
+            }
+        }
+
+        return UninstallReady()
+    }
+
+    fun generateUninstallDetails(): UninstallStage {
+        val messageBuilder = StringBuilder()
+
+        targetAppLabel = targetAppInfo!!.loadSafeLabel(packageManager)
+
+        // If the Activity label differs from the App label, then make sure the user
+        // knows the Activity belongs to the App being uninstalled.
+        if (targetActivityInfo != null) {
+            val activityLabel = targetActivityInfo!!.loadSafeLabel(packageManager)
+            if (!activityLabel.contentEquals(targetAppLabel)) {
+                messageBuilder.append(
+                    context.getString(R.string.uninstall_activity_text, activityLabel)
+                )
+                messageBuilder.append(" ").append(targetAppLabel).append(".\n\n")
+            }
+        }
+
+        val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+        val myUserHandle = Process.myUserHandle()
+        val isSingleUser = isSingleUser()
+
+        if (isUpdate) {
+            messageBuilder.append(context.getString(
+                    if (isSingleUser) R.string.uninstall_update_text
+                    else R.string.uninstall_update_text_multiuser
+                )
+            )
+        } else if (uninstallFromAllUsers && !isSingleUser) {
+            messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
+        } else if (uninstalledUser != myUserHandle) {
+            // Uninstalling user is issuing uninstall for another user
+            val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
+                .getSystemService(UserManager::class.java)
+            val userName = customUserManager!!.userName
+
+            val uninstalledUserType = getUninstalledUserType(myUserHandle, uninstalledUser!!)
+            val messageString: String
+            when (uninstalledUserType) {
+                UserManager.USER_TYPE_PROFILE_MANAGED -> {
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_current_user_work_profile, userName
+                    )
+                }
+
+                UserManager.USER_TYPE_PROFILE_CLONE -> {
+                    isClonedApp = true
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_current_user_clone_profile
+                    )
+                }
+
+                else -> {
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_user, userName
+                    )
+                }
+
+            }
+            messageBuilder.append(messageString)
+        } else if (isCloneProfile(uninstalledUser!!)) {
+            isClonedApp = true
+            messageBuilder.append(context.getString(
+                    R.string.uninstall_application_text_current_user_clone_profile
+                )
+            )
+        } else if (myUserHandle == UserHandle.SYSTEM
+            && hasClonedInstance(targetAppInfo!!.packageName)
+        ) {
+            messageBuilder.append(context.getString(
+                    R.string.uninstall_application_text_with_clone_instance, targetAppLabel
+                )
+            )
+        } else {
+            messageBuilder.append(context.getString(R.string.uninstall_application_text))
+        }
+
+        val message = messageBuilder.toString()
+
+        val title = if (isClonedApp) {
+            context.getString(R.string.cloned_app_label, targetAppLabel)
+        } else {
+            targetAppLabel.toString()
+        }
+
+        var suggestToKeepAppData = false
+        try {
+            val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
+            suggestToKeepAppData =
+                pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
+        }
+
+        var appDataSize: Long = 0
+        if (suggestToKeepAppData) {
+            appDataSize = getAppDataSize(
+                targetPackageName!!,
+                if (uninstallFromAllUsers) null else uninstalledUser
+            )
+        }
+
+        return UninstallUserActionRequired(title, message, appDataSize)
+    }
+
+    /**
+     * Returns whether there is only one "full" user on this device.
+     *
+     * **Note:** On devices that use [headless system user mode]
+     * [android.os.UserManager.isHeadlessSystemUserMode], the system user is not "full",
+     * so it's not be considered in the calculation.
+     */
+    private fun isSingleUser(): Boolean {
+        val userCount = userManager!!.userCount
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
+    }
+
+    /**
+     * Returns the type of the user from where an app is being uninstalled. We are concerned with
+     * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
+     * belong to the same profile group.
+     */
+    private fun getUninstalledUserType(
+        myUserHandle: UserHandle,
+        uninstalledUserHandle: UserHandle
+    ): String? {
+        if (!userManager!!.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
+            return null
+        }
+        val customUserManager = context.createContextAsUser(uninstalledUserHandle, 0)
+            .getSystemService(UserManager::class.java)
+        val userTypes =
+            arrayOf(UserManager.USER_TYPE_PROFILE_MANAGED, UserManager.USER_TYPE_PROFILE_CLONE)
+
+        for (userType in userTypes) {
+            if (customUserManager!!.isUserOfType(userType)) {
+                return userType
+            }
+        }
+        return null
+    }
+
+    private fun hasClonedInstance(packageName: String): Boolean {
+        // Check if clone user is present on the device.
+        var cloneUser: UserHandle? = null
+        val profiles = userManager!!.userProfiles
+
+        for (userHandle in profiles) {
+            if (userHandle != UserHandle.SYSTEM && isCloneProfile(userHandle)) {
+                cloneUser = userHandle
+                break
+            }
+        }
+        // Check if another instance of given package exists in clone user profile.
+        return try {
+            cloneUser != null
+                && packageManager.getPackageUidAsUser(
+                packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
+                ) > 0
+        } catch (e: PackageManager.NameNotFoundException) {
+            false
+        }
+    }
+
+    private fun isCloneProfile(userHandle: UserHandle): Boolean {
+        val customUserManager = context.createContextAsUser(userHandle, 0)
+            .getSystemService(UserManager::class.java)
+        return customUserManager!!.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to or `null` if files of all users should
+     * be counted.
+     * @return The number of bytes.
+     */
+    private fun getAppDataSize(pkg: String, user: UserHandle?): Long {
+        if (user != null) {
+            return getAppDataSizeForUser(pkg, user)
+        }
+        // We are uninstalling from all users. Get cumulative app data size for all users.
+        val userHandles = userManager!!.getUserHandles(true)
+        var totalAppDataSize: Long = 0
+        val numUsers = userHandles.size
+        for (i in 0 until numUsers) {
+            totalAppDataSize += getAppDataSizeForUser(pkg, userHandles[i])
+        }
+        return totalAppDataSize
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to
+     * @return The number of bytes.
+     */
+    private fun getAppDataSizeForUser(pkg: String, user: UserHandle): Long {
+        val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
+        try {
+            val stats = storageStatsManager!!.queryStatsForPackage(
+                packageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user
+            )
+            return stats.getDataBytes()
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Cannot determine amount of app data for $pkg", e)
+        }
+        return 0
+    }
+
+    fun initiateUninstall(keepData: Boolean) {
+        // Get an uninstallId to track results and show a notification on non-TV devices.
+        uninstallId = try {
+            UninstallEventReceiver.addObserver(
+                context, EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult
+            )
+        } catch (e: OutOfIdsException) {
+            Log.e(LOG_TAG, "Failed to start uninstall", e)
+            handleUninstallResult(
+                PackageInstaller.STATUS_FAILURE,
+                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+            )
+            return
+        }
+
+        // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
+        uninstallResult.value = UninstallUninstalling(targetAppLabel, isClonedApp)
+
+        val uninstallData = Bundle()
+        uninstallData.putInt(EXTRA_UNINSTALL_ID, uninstallId)
+        uninstallData.putString(EXTRA_PACKAGE_NAME, targetPackageName)
+        uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, uninstallFromAllUsers)
+        uninstallData.putCharSequence(EXTRA_APP_LABEL, targetAppLabel)
+        uninstallData.putBoolean(EXTRA_IS_CLONE_APP, isClonedApp)
+        Log.i(LOG_TAG, "Uninstalling extras = $uninstallData")
+
+        // Get a PendingIntent for result broadcast and issue an uninstall request
+        val broadcastIntent = Intent(BROADCAST_ACTION)
+        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
+        broadcastIntent.setPackage(context.packageName)
+        val pendingIntent = PendingIntent.getBroadcast(
+            context, uninstallId, broadcastIntent,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+        )
+        if (!startUninstall(
+                targetPackageName!!, uninstalledUser!!, pendingIntent, uninstallFromAllUsers,
+                keepData
+            )
+        ) {
+            handleUninstallResult(
+                PackageInstaller.STATUS_FAILURE,
+                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+            )
+        }
+    }
+
+    private fun handleUninstallResult(
+        status: Int,
+        legacyStatus: Int,
+        message: String?,
+        serviceId: Int
+    ) {
+        if (callback != null) {
+            // The caller will be informed about the result via a callback
+            callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
+
+            // Since the caller already received the results, just finish the app at this point
+            uninstallResult.value = null
+            return
+        }
+        val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+        if (returnResult || callingActivity != null) {
+            val intent = Intent()
+            intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                uninstallResult.setValue(
+                    UninstallSuccess(resultIntent = intent, activityResultCode = Activity.RESULT_OK)
+                )
+            } else {
+                uninstallResult.setValue(
+                    UninstallFailed(
+                        returnResult = true,
+                        resultIntent = intent,
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                )
+            }
+            return
+        }
+
+        // Caller did not want the result back. So, we either show a Toast, or a Notification.
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            val statusMessage = if (isClonedApp) context.getString(
+                R.string.uninstall_done_clone_app, targetAppLabel
+            ) else context.getString(R.string.uninstall_done_app, targetAppLabel)
+            uninstallResult.setValue(
+                UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
+            )
+        } else {
+            val uninstallFailureChannel = NotificationChannel(
+                UNINSTALL_FAILURE_CHANNEL,
+                context.getString(R.string.uninstall_failure_notification_channel),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            notificationManager!!.createNotificationChannel(uninstallFailureChannel)
+
+            val uninstallFailedNotification: Notification.Builder =
+                Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL)
+
+            val myUserHandle = Process.myUserHandle()
+            when (legacyStatus) {
+                PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
+                    // Find out if the package is an active admin for some non-current user.
+                    val otherBlockingUserHandle =
+                        findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
+                    if (otherBlockingUserHandle == null) {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed because $targetPackageName"
+                                + " is a device admin"
+                        )
+                        addDeviceManagerButton(context, uninstallFailedNotification)
+                        setBigText(
+                            uninstallFailedNotification, context.getString(
+                                R.string.uninstall_failed_device_policy_manager
+                            )
+                        )
+                    } else {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed because $targetPackageName"
+                                + " is a device admin of user $otherBlockingUserHandle"
+                        )
+                        val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
+                            .getSystemService(UserManager::class.java)!!.userName
+                        setBigText(
+                            uninstallFailedNotification, String.format(
+                                context.getString(
+                                    R.string.uninstall_failed_device_policy_manager_of_user
+                                ), userName
+                            )
+                        )
+                    }
+                }
+
+                PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
+                    val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
+                    val isProfileOfOrSame = isProfileOfOrSame(
+                        userManager!!, myUserHandle, otherBlockingUserHandle
+                    )
+                    if (isProfileOfOrSame) {
+                        addDeviceManagerButton(context, uninstallFailedNotification)
+                    } else {
+                        addManageUsersButton(context, uninstallFailedNotification)
+                    }
+                    var bigText: String? = null
+                    if (otherBlockingUserHandle == null) {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed for $targetPackageName " +
+                                "with code $status no blocking user"
+                        )
+                    } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
+                        bigText = context.getString(R.string.uninstall_blocked_device_owner)
+                    } else {
+                        bigText = context.getString(
+                            if (uninstallFromAllUsers) R.string.uninstall_all_blocked_profile_owner
+                            else R.string.uninstall_blocked_profile_owner
+                        )
+                    }
+                    bigText?.let { setBigText(uninstallFailedNotification, it) }
+                }
+
+                else -> {
+                    Log.d(
+                        LOG_TAG, "Uninstall blocked for $targetPackageName"
+                            + " with legacy code $legacyStatus"
+                    )
+                }
+            }
+            uninstallFailedNotification.setContentTitle(
+                context.getString(R.string.uninstall_failed_app, targetAppLabel)
+            )
+            uninstallFailedNotification.setOngoing(false)
+            uninstallFailedNotification.setSmallIcon(R.drawable.ic_error)
+
+            uninstallResult.setValue(
+                UninstallFailed(
+                    returnResult = false,
+                    uninstallNotificationId = uninstallId,
+                    uninstallNotification = uninstallFailedNotification.build()
+                )
+            )
+        }
+    }
+
+    /**
+     * @param myUserHandle [UserHandle] of the current user.
+     * @param packageName Name of the package being uninstalled.
+     * @return the [UserHandle] of the user in which a package is a device admin.
+     */
+    private fun findUserOfDeviceAdmin(myUserHandle: UserHandle, packageName: String): UserHandle? {
+        for (otherUserHandle in userManager!!.getUserHandles(true)) {
+            // We only catch the case when the user in question is neither the
+            // current user nor its profile.
+            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+                continue
+            }
+            val dpm = context.createContextAsUser(otherUserHandle, 0)
+                .getSystemService(DevicePolicyManager::class.java)
+            if (dpm!!.packageHasActiveAdmins(packageName)) {
+                return otherUserHandle
+            }
+        }
+        return null
+    }
+
+    /**
+     *
+     * @param packageName Name of the package being uninstalled.
+     * @return [UserHandle] of the user in which a package is blocked from being uninstalled.
+     */
+    private fun findBlockingUser(packageName: String): UserHandle? {
+        for (otherUserHandle in userManager!!.getUserHandles(true)) {
+            // TODO (b/307399586): Add a negation when the logic of the method is fixed
+            if (packageManager.canUserUninstall(packageName, otherUserHandle)) {
+                return otherUserHandle
+            }
+        }
+        return null
+    }
+
+    /**
+     * Set big text for the notification.
+     *
+     * @param builder The builder of the notification
+     * @param text The text to set.
+     */
+    private fun setBigText(
+        builder: Notification.Builder,
+        text: CharSequence
+    ) {
+        builder.setStyle(Notification.BigTextStyle().bigText(text))
+    }
+
+    /**
+     * Add a button to the notification that links to the user management.
+     *
+     * @param context The context the notification is created in
+     * @param builder The builder of the notification
+     */
+    private fun addManageUsersButton(
+        context: Context,
+        builder: Notification.Builder
+    ) {
+        builder.addAction(
+            Notification.Action.Builder(
+                Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
+                context.getString(R.string.manage_users),
+                PendingIntent.getActivity(
+                    context, 0, getUserSettingsIntent(),
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                )
+            )
+                .build()
+        )
+    }
+
+    private fun getUserSettingsIntent(): Intent {
+        val intent = Intent(Settings.ACTION_USER_SETTINGS)
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+        return intent
+    }
+
+    /**
+     * Add a button to the notification that links to the device policy management.
+     *
+     * @param context The context the notification is created in
+     * @param builder The builder of the notification
+     */
+    private fun addDeviceManagerButton(
+        context: Context,
+        builder: Notification.Builder
+    ) {
+        builder.addAction(
+            Notification.Action.Builder(
+                Icon.createWithResource(context, R.drawable.ic_lock),
+                context.getString(R.string.manage_device_administrators),
+                PendingIntent.getActivity(
+                    context, 0, getDeviceManagerIntent(),
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                )
+            )
+                .build()
+        )
+    }
+
+    private fun getDeviceManagerIntent(): Intent {
+        val intent = Intent()
+        intent.setClassName(
+            "com.android.settings",
+            "com.android.settings.Settings\$DeviceAdminSettingsActivity"
+        )
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+        return intent
+    }
+
+    /**
+     * Starts an uninstall for the given package.
+     *
+     * @return `true` if there was no exception while uninstalling. This does not represent
+     * the result of the uninstall. Result will be made available in [handleUninstallResult]
+     */
+    private fun startUninstall(
+        packageName: String,
+        targetUser: UserHandle,
+        pendingIntent: PendingIntent,
+        uninstallFromAllUsers: Boolean,
+        keepData: Boolean
+    ): Boolean {
+        var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
+        flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
+
+        return try {
+            context.createContextAsUser(targetUser, 0)
+                .packageManager.packageInstaller.uninstall(
+                    VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    flags, pendingIntent.intentSender
+                )
+            true
+        } catch (e: IllegalArgumentException) {
+            Log.e(LOG_TAG, "Failed to uninstall", e)
+            false
+        }
+    }
+
+    fun cancelInstall() {
+        if (callback != null) {
+            callback!!.onUninstallComplete(
+                targetPackageName!!,
+                PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"
+            )
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = UninstallRepository::class.java.simpleName
+        private const val UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"
+        private const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"
+        private const val EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"
+        private const val EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"
+        private const val EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"
+        private const val EXTRA_PACKAGE_NAME =
+            "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"
+    }
+
+    class CallerInfo(val activityName: String?, val uid: Int)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
new file mode 100644
index 0000000..f086209
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.app.Notification
+import android.content.Intent
+import com.android.packageinstaller.R
+
+sealed class UninstallStage(val stageCode: Int) {
+
+    companion object {
+        const val STAGE_DEFAULT = -1
+        const val STAGE_ABORTED = 0
+        const val STAGE_READY = 1
+        const val STAGE_USER_ACTION_REQUIRED = 2
+        const val STAGE_UNINSTALLING = 3
+        const val STAGE_SUCCESS = 4
+        const val STAGE_FAILED = 5
+    }
+}
+
+class UninstallReady : UninstallStage(STAGE_READY)
+
+data class UninstallUserActionRequired(
+    val title: String? = null,
+    val message: String? = null,
+    val appDataSize: Long = 0
+) : UninstallStage(STAGE_USER_ACTION_REQUIRED)
+
+data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) :
+    UninstallStage(STAGE_UNINSTALLING)
+
+data class UninstallSuccess(
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = 0,
+    val message: String? = null,
+) : UninstallStage(STAGE_SUCCESS)
+
+data class UninstallFailed(
+    val returnResult: Boolean,
+    /**
+     * If the caller wants the result back, the intent will hold the uninstall failure status code
+     * and legacy code.
+     */
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = Activity.RESULT_CANCELED,
+    /**
+     * ID used to show [uninstallNotification]
+     */
+    val uninstallNotificationId: Int? = null,
+    /**
+     * When the user does not request a result back, this notification will be shown indicating the
+     * reason for uninstall failure.
+     */
+    val uninstallNotification: Notification? = null,
+) : UninstallStage(STAGE_FAILED) {
+
+    init {
+        if (uninstallNotification != null && uninstallNotificationId == null) {
+            throw IllegalArgumentException(
+                "uninstallNotification cannot be set without uninstallNotificationId"
+            )
+        }
+    }
+}
+
+data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED) {
+
+    var dialogTitleResource = 0
+    var dialogTextResource = 0
+    val activityResultCode = Activity.RESULT_FIRST_USER
+
+    init {
+        when (abortReason) {
+            ABORT_REASON_APP_UNAVAILABLE -> {
+                dialogTitleResource = R.string.app_not_found_dlg_title
+                dialogTextResource = R.string.app_not_found_dlg_text
+            }
+
+            ABORT_REASON_USER_NOT_ALLOWED -> {
+                dialogTitleResource = 0
+                dialogTextResource = R.string.user_is_not_allowed_dlg_text
+            }
+
+            else -> {
+                dialogTitleResource = 0
+                dialogTextResource = R.string.generic_error_dlg_text
+            }
+        }
+    }
+
+    companion object {
+        const val ABORT_REASON_GENERIC_ERROR = 0
+        const val ABORT_REASON_APP_UNAVAILABLE = 1
+        const val ABORT_REASON_USER_NOT_ALLOWED = 2
+    }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
deleted file mode 100644
index 520b6c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class InstallAborted extends InstallStage {
-
-    public static final int ABORT_REASON_INTERNAL_ERROR = 0;
-    public static final int ABORT_REASON_POLICY = 1;
-    public static final int ABORT_REASON_DONE = 2;
-    public static final int DLG_PACKAGE_ERROR = 1;
-    private final int mStage = InstallStage.STAGE_ABORTED;
-    private final int mAbortReason;
-
-    /**
-     * It will hold the restriction name, when the restriction was enforced by the system, and not
-     * a device admin.
-     */
-    @NonNull
-    private final String mMessage;
-    /**
-     * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent
-     * to display a support dialog when a feature was disabled by an admin. It will be
-     * {@code null} if the feature is disabled by the system. In this case, the restriction name
-     * will be set in {@link #mMessage} </p>
-     *
-     * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an
-     * intent to be sent as a result to the calling activity.</p>
-     */
-    @Nullable
-    private final Intent mIntent;
-    private final int mErrorDialogType;
-    private final int mActivityResultCode;
-
-    private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent,
-        int activityResultCode, int errorDialogType) {
-        mAbortReason = reason;
-        mMessage = message;
-        mIntent = intent;
-        mErrorDialogType = errorDialogType;
-        mActivityResultCode = activityResultCode;
-    }
-
-    public int getAbortReason() {
-        return mAbortReason;
-    }
-
-    @NonNull
-    public String getMessage() {
-        return mMessage;
-    }
-
-    @Nullable
-    public Intent getResultIntent() {
-        return mIntent;
-    }
-
-    public int getErrorDialogType() {
-        return mErrorDialogType;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private final int mAbortReason;
-        private String mMessage = "";
-        private Intent mIntent = null;
-        private int mActivityResultCode = Activity.RESULT_CANCELED;
-        private int mErrorDialogType;
-
-        public Builder(int reason) {
-            mAbortReason = reason;
-        }
-
-        public Builder setMessage(@NonNull String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public Builder setResultIntent(@NonNull Intent intent) {
-            mIntent = intent;
-            return this;
-        }
-
-        public Builder setErrorDialogType(int dialogType) {
-            mErrorDialogType = dialogType;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public InstallAborted build() {
-            return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode,
-                mErrorDialogType);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
deleted file mode 100644
index 67e1690..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallFailed extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_FAILED;
-    @NonNull
-    private final AppSnippet mAppSnippet;
-    private final int mStatusCode;
-    private final int mLegacyCode;
-    @Nullable
-    private final String mMessage;
-
-    public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode,
-        @Nullable String message) {
-        mAppSnippet = appSnippet;
-        mLegacyCode = statusCode;
-        mStatusCode = legacyCode;
-        mMessage = message;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-
-    public int getStatusCode() {
-        return mStatusCode;
-    }
-
-    public int getLegacyCode() {
-        return mLegacyCode;
-    }
-
-    @Nullable
-    public String getMessage() {
-        return mMessage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
deleted file mode 100644
index efd4947..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallInstalling extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_INSTALLING;
-    @NonNull
-    private final AppSnippet mAppSnippet;
-
-    public InstallInstalling(@NonNull AppSnippet appSnippet) {
-        mAppSnippet = appSnippet;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
deleted file mode 100644
index 548f2c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.installstagedata;
-
-public class InstallReady extends InstallStage{
-
-    private final int mStage = InstallStage.STAGE_READY;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
deleted file mode 100644
index f91e64b..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-public abstract class InstallStage {
-
-    public static final int STAGE_DEFAULT = -1;
-    public static final int STAGE_ABORTED = 0;
-    public static final int STAGE_STAGING = 1;
-    public static final int STAGE_READY = 2;
-    public static final int STAGE_USER_ACTION_REQUIRED = 3;
-    public static final int STAGE_INSTALLING = 4;
-    public static final int STAGE_SUCCESS = 5;
-    public static final int STAGE_FAILED = 6;
-
-    /**
-     * @return the integer value representing current install stage.
-     */
-    public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
deleted file mode 100644
index da48256..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallSuccess extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_SUCCESS;
-
-    @NonNull
-    private final AppSnippet mAppSnippet;
-    private final boolean mShouldReturnResult;
-    /**
-     * <p>If the caller is requesting a result back, this will hold the Intent with
-     * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p>
-     * <p>If the caller doesn't want the result back, this will hold the Intent that launches
-     * the newly installed / updated app.</p>
-     */
-    @NonNull
-    private final Intent mResultIntent;
-
-    public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult,
-        @NonNull Intent launcherIntent) {
-        mAppSnippet = appSnippet;
-        mShouldReturnResult = shouldReturnResult;
-        mResultIntent = launcherIntent;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-
-    public boolean shouldReturnResult() {
-        return mShouldReturnResult;
-    }
-
-    @NonNull
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public static class Builder {
-
-        private final AppSnippet mAppSnippet;
-        private boolean mShouldReturnResult;
-        private Intent mLauncherIntent;
-
-        public Builder(@NonNull AppSnippet appSnippet) {
-            mAppSnippet = appSnippet;
-        }
-
-        public Builder setShouldReturnResult(boolean returnResult) {
-            mShouldReturnResult = returnResult;
-            return this;
-        }
-
-        public Builder setResultIntent(@NonNull Intent intent) {
-            mLauncherIntent = intent;
-            return this;
-        }
-
-        public InstallSuccess build() {
-            return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
deleted file mode 100644
index 08a7487..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallUserActionRequired extends InstallStage {
-
-    public static final int USER_ACTION_REASON_UNKNOWN_SOURCE = 0;
-    public static final int USER_ACTION_REASON_ANONYMOUS_SOURCE = 1;
-    public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
-    private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
-    private final int mActionReason;
-    @Nullable
-    private final AppSnippet mAppSnippet;
-    private final boolean mIsAppUpdating;
-    @Nullable
-    private final String mDialogMessage;
-
-    public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet,
-        boolean isUpdating, @Nullable String dialogMessage) {
-        mActionReason = actionReason;
-        mAppSnippet = appSnippet;
-        mIsAppUpdating = isUpdating;
-        mDialogMessage = dialogMessage;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @Nullable
-    public Drawable getAppIcon() {
-        return mAppSnippet != null ? mAppSnippet.getIcon() : null;
-    }
-
-    @Nullable
-    public String getAppLabel() {
-        return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null;
-    }
-
-    public boolean isAppUpdating() {
-        return mIsAppUpdating;
-    }
-
-    @Nullable
-    public String getDialogMessage() {
-        return mDialogMessage;
-    }
-
-    public int getActionReason() {
-        return mActionReason;
-    }
-
-    public static class Builder {
-
-        private final int mActionReason;
-        private final AppSnippet mAppSnippet;
-        private boolean mIsAppUpdating;
-        private String mDialogMessage;
-
-        public Builder(int actionReason, @Nullable AppSnippet appSnippet) {
-            mActionReason = actionReason;
-            mAppSnippet = appSnippet;
-        }
-
-        public Builder setAppUpdating(boolean isUpdating) {
-            mIsAppUpdating = isUpdating;
-            return this;
-        }
-
-        public Builder setDialogMessage(@Nullable String message) {
-            mDialogMessage = message;
-            return this;
-        }
-
-        public InstallUserActionRequired build() {
-            return new InstallUserActionRequired(mActionReason, mAppSnippet, mIsAppUpdating,
-                mDialogMessage);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
deleted file mode 100644
index 9aea6b1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import com.android.packageinstaller.R;
-
-public class UninstallAborted extends UninstallStage {
-
-    public static final int ABORT_REASON_GENERIC_ERROR = 0;
-    public static final int ABORT_REASON_APP_UNAVAILABLE = 1;
-    public static final int ABORT_REASON_USER_NOT_ALLOWED = 2;
-    private final int mStage = UninstallStage.STAGE_ABORTED;
-    private final int mAbortReason;
-    private final int mDialogTitleResource;
-    private final int mDialogTextResource;
-    private final int mActivityResultCode = Activity.RESULT_FIRST_USER;
-
-    public UninstallAborted(int abortReason) {
-        mAbortReason = abortReason;
-        switch (abortReason) {
-            case ABORT_REASON_APP_UNAVAILABLE -> {
-                mDialogTitleResource = R.string.app_not_found_dlg_title;
-                mDialogTextResource = R.string.app_not_found_dlg_text;
-            }
-            case ABORT_REASON_USER_NOT_ALLOWED -> {
-                mDialogTitleResource = 0;
-                mDialogTextResource = R.string.user_is_not_allowed_dlg_text;
-            }
-            default -> {
-                mDialogTitleResource = 0;
-                mDialogTextResource = R.string.generic_error_dlg_text;
-            }
-        }
-    }
-
-    public int getAbortReason() {
-        return mAbortReason;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    public int getDialogTitleResource() {
-        return mDialogTitleResource;
-    }
-
-    public int getDialogTextResource() {
-        return mDialogTextResource;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
deleted file mode 100644
index 6ed8883..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.Intent;
-
-public class UninstallFailed extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_FAILED;
-    private final boolean mReturnResult;
-    /**
-     * If the caller wants the result back, the intent will hold the uninstall failure status code
-     * and legacy code.
-     */
-    private final Intent mResultIntent;
-    /**
-     * When the user does not request a result back, this notification will be shown indicating the
-     * reason for uninstall failure.
-     */
-    private final Notification mUninstallNotification;
-    /**
-     * ID used to show {@link #mUninstallNotification}
-     */
-    private final int mUninstallId;
-    private final int mActivityResultCode;
-
-    public UninstallFailed(boolean returnResult, Intent resultIntent, int activityResultCode,
-        int uninstallId, Notification uninstallNotification) {
-        mReturnResult = returnResult;
-        mResultIntent = resultIntent;
-        mActivityResultCode = activityResultCode;
-        mUninstallId = uninstallId;
-        mUninstallNotification = uninstallNotification;
-    }
-
-    public boolean returnResult() {
-        return mReturnResult;
-    }
-
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    public Notification getUninstallNotification() {
-        return mUninstallNotification;
-    }
-
-    public int getUninstallId() {
-        return mUninstallId;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private final boolean mReturnResult;
-        private int mActivityResultCode = Activity.RESULT_CANCELED;
-        /**
-         * See {@link UninstallFailed#mResultIntent}
-         */
-        private Intent mResultIntent = null;
-        /**
-         * See {@link UninstallFailed#mUninstallNotification}
-         */
-        private Notification mUninstallNotification;
-        /**
-         * See {@link UninstallFailed#mUninstallId}
-         */
-        private int mUninstallId;
-
-        public Builder(boolean returnResult) {
-            mReturnResult = returnResult;
-        }
-
-        public Builder setUninstallNotification(int uninstallId, Notification notification) {
-            mUninstallId = uninstallId;
-            mUninstallNotification = notification;
-            return this;
-        }
-
-        public Builder setResultIntent(Intent intent) {
-            mResultIntent = intent;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public UninstallFailed build() {
-            return new UninstallFailed(mReturnResult, mResultIntent, mActivityResultCode,
-                mUninstallId, mUninstallNotification);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
deleted file mode 100644
index 0108cb4..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallReady extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_READY;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
deleted file mode 100644
index 87ca4ec..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.uninstallstagedata;
-
-public abstract class UninstallStage {
-
-    public static final int STAGE_DEFAULT = -1;
-    public static final int STAGE_ABORTED = 0;
-    public static final int STAGE_READY = 1;
-    public static final int STAGE_USER_ACTION_REQUIRED = 2;
-    public static final int STAGE_UNINSTALLING = 3;
-    public static final int STAGE_SUCCESS = 4;
-    public static final int STAGE_FAILED = 5;
-
-    public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
deleted file mode 100644
index 5df6b02..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.content.Intent;
-
-public class UninstallSuccess extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_SUCCESS;
-    private final String mMessage;
-    private final Intent mResultIntent;
-    private final int mActivityResultCode;
-
-    public UninstallSuccess(Intent resultIntent, int activityResultCode, String message) {
-        mResultIntent = resultIntent;
-        mActivityResultCode = activityResultCode;
-        mMessage = message;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private Intent mResultIntent;
-        private int mActivityResultCode;
-        private String mMessage;
-
-        public Builder() {
-        }
-
-        public Builder setResultIntent(Intent intent) {
-            mResultIntent = intent;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public Builder setMessage(String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public UninstallSuccess build() {
-            return new UninstallSuccess(mResultIntent, mActivityResultCode, mMessage);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
deleted file mode 100644
index f5156cb..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUninstalling extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_UNINSTALLING;
-
-    private final CharSequence mAppLabel;
-    private final boolean mIsCloneUser;
-
-    public UninstallUninstalling(CharSequence appLabel, boolean isCloneUser) {
-        mAppLabel = appLabel;
-        mIsCloneUser = isCloneUser;
-    }
-
-    public CharSequence getAppLabel() {
-        return mAppLabel;
-    }
-
-    public boolean isCloneUser() {
-        return mIsCloneUser;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
deleted file mode 100644
index b600149..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUserActionRequired extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED;
-    private final String mTitle;
-    private final String mMessage;
-    private final long mAppDataSize;
-
-    public UninstallUserActionRequired(String title, String message, long appDataSize) {
-        mTitle = title;
-        mMessage = message;
-        mAppDataSize = appDataSize;
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public long getAppDataSize() {
-        return mAppDataSize;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private String mTitle;
-        private String mMessage;
-        private long mAppDataSize = 0;
-
-        public Builder setTitle(String title) {
-            mTitle = title;
-            return this;
-        }
-
-        public Builder setMessage(String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public Builder setAppDataSize(long appDataSize) {
-            mAppDataSize = appDataSize;
-            return this;
-        }
-
-        public UninstallUserActionRequired build() {
-            return new UninstallUserActionRequired(mTitle, mMessage, mAppDataSize);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
deleted file mode 100644
index fdb024f..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.ui;
-
-import android.content.Intent;
-
-public interface InstallActionListener {
-
-    /**
-     * Method to handle a positive response from the user
-     */
-    void onPositiveResponse(int stageCode);
-
-    /**
-     * Method to dispatch intent for toggling "install from unknown sources" setting for a package
-     */
-    void sendUnknownAppsIntent(String packageName);
-
-    /**
-     * Method to handle a negative response from the user
-     */
-    void onNegativeResponse(int stageCode);
-    void openInstalledApp(Intent intent);
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
new file mode 100644
index 0000000..c109fc6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.ui
+
+import android.content.Intent
+
+interface InstallActionListener {
+    /**
+     * Method to handle a positive response from the user.
+     */
+    fun onPositiveResponse(reasonCode: Int)
+
+    /**
+     * Method to dispatch intent for toggling "install from unknown sources" setting for a package.
+     */
+    fun sendUnknownAppsIntent(sourcePackageName: String)
+
+    /**
+     * Method to handle a negative response from the user.
+     */
+    fun onNegativeResponse(stageCode: Int)
+
+    /**
+     * Launch the intent to open the newly installed / updated app.
+     */
+    fun openInstalledApp(intent: Intent?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
deleted file mode 100644
index d06b4b3..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.ui;
-
-import static android.content.Intent.CATEGORY_LAUNCHER;
-import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.os.Process.INVALID_UID;
-import static com.android.packageinstaller.v2.model.InstallRepository.EXTRA_STAGED_SESSION_ID;
-
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Window;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment;
-import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment;
-import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-public class InstallLaunch extends FragmentActivity implements InstallActionListener {
-
-    public static final String EXTRA_CALLING_PKG_UID =
-            InstallLaunch.class.getPackageName() + ".callingPkgUid";
-    public static final String EXTRA_CALLING_PKG_NAME =
-            InstallLaunch.class.getPackageName() + ".callingPkgName";
-    private static final String TAG = InstallLaunch.class.getSimpleName();
-    private static final String TAG_DIALOG = "dialog";
-    private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
-    private final boolean mLocalLOGV = false;
-    /**
-     * A collection of unknown sources listeners that are actively listening for app ops mode
-     * changes
-     */
-    private final List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1);
-    private InstallViewModel mInstallViewModel;
-    private InstallRepository mInstallRepository;
-    private FragmentManager mFragmentManager;
-    private AppOpsManager mAppOpsManager;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
-
-        mFragmentManager = getSupportFragmentManager();
-        mAppOpsManager = getSystemService(AppOpsManager.class);
-
-        mInstallRepository = new InstallRepository(getApplicationContext());
-        mInstallViewModel = new ViewModelProvider(this,
-                new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get(
-                InstallViewModel.class);
-
-        Intent intent = getIntent();
-        CallerInfo info = new CallerInfo(
-                intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
-                intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
-        mInstallViewModel.preprocessIntent(intent, info);
-
-        mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange);
-    }
-
-    /**
-     * Main controller of the UI. This method shows relevant dialogs based on the install stage
-     */
-    private void onInstallStageChange(InstallStage installStage) {
-        switch (installStage.getStageCode()) {
-            case InstallStage.STAGE_STAGING -> {
-                InstallStagingFragment stagingDialog = new InstallStagingFragment();
-                showDialogInner(stagingDialog);
-                mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
-            }
-            case InstallStage.STAGE_ABORTED -> {
-                InstallAborted aborted = (InstallAborted) installStage;
-                switch (aborted.getAbortReason()) {
-                    // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR
-                    case InstallAborted.ABORT_REASON_DONE,
-                        InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
-                        setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true);
-                    case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
-                    default -> setResult(RESULT_CANCELED, null, true);
-                }
-            }
-            case InstallStage.STAGE_USER_ACTION_REQUIRED -> {
-                InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
-                switch (uar.getActionReason()) {
-                    case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
-                        InstallConfirmationFragment actionDialog =
-                            new InstallConfirmationFragment(uar);
-                        showDialogInner(actionDialog);
-                    }
-                    case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
-                        ExternalSourcesBlockedFragment externalSourceDialog =
-                            new ExternalSourcesBlockedFragment(uar);
-                        showDialogInner(externalSourceDialog);
-                    }
-                    case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
-                        AnonymousSourceFragment anonymousSourceDialog =
-                            new AnonymousSourceFragment();
-                        showDialogInner(anonymousSourceDialog);
-                    }
-                }
-            }
-            case InstallStage.STAGE_INSTALLING -> {
-                InstallInstalling installing = (InstallInstalling) installStage;
-                InstallInstallingFragment installingDialog =
-                    new InstallInstallingFragment(installing);
-                showDialogInner(installingDialog);
-            }
-            case InstallStage.STAGE_SUCCESS -> {
-                InstallSuccess success = (InstallSuccess) installStage;
-                if (success.shouldReturnResult()) {
-                    Intent successIntent = success.getResultIntent();
-                    setResult(Activity.RESULT_OK, successIntent, true);
-                } else {
-                    InstallSuccessFragment successFragment = new InstallSuccessFragment(success);
-                    showDialogInner(successFragment);
-                }
-            }
-            case InstallStage.STAGE_FAILED -> {
-                InstallFailed failed = (InstallFailed) installStage;
-                InstallFailedFragment failedDialog = new InstallFailedFragment(failed);
-                showDialogInner(failedDialog);
-            }
-            default -> {
-                Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
-                showDialogInner(null);
-            }
-        }
-    }
-
-    private void showPolicyRestrictionDialog(InstallAborted aborted) {
-        String restriction = aborted.getMessage();
-        Intent adminSupportIntent = aborted.getResultIntent();
-        boolean shouldFinish;
-
-        // If the given restriction is set by an admin, display information about the
-        // admin enforcing the restriction for the affected user. If not enforced by the admin,
-        // show the system dialog.
-        if (adminSupportIntent != null) {
-            if (mLocalLOGV) {
-                Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent);
-            }
-            startActivity(adminSupportIntent);
-            // Finish the package installer app since the next dialog will not be shown by this app
-            shouldFinish = true;
-        } else {
-            if (mLocalLOGV) {
-                Log.i(TAG, "Restriction set by system: " + restriction);
-            }
-            DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction);
-            // Don't finish the package installer app since the next dialog
-            // will be shown by this app
-            shouldFinish = false;
-            showDialogInner(blockedByPolicyDialog);
-        }
-        setResult(RESULT_CANCELED, null, shouldFinish);
-    }
-
-    /**
-     * Create a new dialog based on the install restriction enforced.
-     *
-     * @param restriction The restriction to create the dialog for
-     * @return The dialog
-     */
-    private DialogFragment createDevicePolicyRestrictionDialog(String restriction) {
-        if (mLocalLOGV) {
-            Log.i(TAG, "createDialog(" + restriction + ")");
-        }
-        return switch (restriction) {
-            case UserManager.DISALLOW_INSTALL_APPS ->
-                new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text);
-            case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
-                new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text);
-            default -> null;
-        };
-    }
-
-    /**
-     * Replace any visible dialog by the dialog returned by InstallRepository
-     *
-     * @param newDialog The new dialog to display
-     */
-    private void showDialogInner(@Nullable DialogFragment newDialog) {
-        DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
-            TAG_DIALOG);
-        if (currentDialog != null) {
-            currentDialog.dismissAllowingStateLoss();
-        }
-        if (newDialog != null) {
-            newDialog.show(mFragmentManager, TAG_DIALOG);
-        }
-    }
-
-    public void setResult(int resultCode, Intent data, boolean shouldFinish) {
-        super.setResult(resultCode, data);
-        if (shouldFinish) {
-            finish();
-        }
-    }
-
-    @Override
-    public void onPositiveResponse(int reasonCode) {
-        switch (reasonCode) {
-            case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
-                mInstallViewModel.forcedSkipSourceCheck();
-            case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
-                mInstallViewModel.initiateInstall();
-        }
-    }
-
-    @Override
-    public void onNegativeResponse(int stageCode) {
-        if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
-            mInstallViewModel.cleanupInstall();
-        }
-        setResult(Activity.RESULT_CANCELED, null, true);
-    }
-
-    @Override
-    public void sendUnknownAppsIntent(String sourcePackageName) {
-        Intent settingsIntent = new Intent();
-        settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
-        final Uri packageUri = Uri.parse("package:" + sourcePackageName);
-        settingsIntent.setData(packageUri);
-        settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY);
-
-        try {
-            registerAppOpChangeListener(new UnknownSourcesListener(sourcePackageName),
-                sourcePackageName);
-            startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE);
-        } catch (ActivityNotFoundException exc) {
-            Log.e(TAG, "Settings activity not found for action: "
-                + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
-        }
-    }
-
-    @Override
-    public void openInstalledApp(Intent intent) {
-        setResult(RESULT_OK, intent, true);
-        if (intent != null && intent.hasCategory(CATEGORY_LAUNCHER)) {
-            startActivity(intent);
-        }
-    }
-
-    private void registerAppOpChangeListener(UnknownSourcesListener listener, String packageName) {
-        mAppOpsManager.startWatchingMode(
-            AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, packageName,
-            listener);
-        mActiveUnknownSourcesListeners.add(listener);
-    }
-
-    private void unregisterAppOpChangeListener(UnknownSourcesListener listener) {
-        mActiveUnknownSourcesListeners.remove(listener);
-        mAppOpsManager.stopWatchingMode(listener);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == REQUEST_TRUST_EXTERNAL_SOURCE) {
-            mInstallViewModel.reattemptInstall();
-        } else {
-            setResult(Activity.RESULT_CANCELED,  null, true);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        while (!mActiveUnknownSourcesListeners.isEmpty()) {
-            unregisterAppOpChangeListener(mActiveUnknownSourcesListeners.get(0));
-        }
-    }
-
-    private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener {
-
-        private final String mOriginatingPackage;
-
-        public UnknownSourcesListener(String originatingPackage) {
-            mOriginatingPackage = originatingPackage;
-        }
-
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            if (!mOriginatingPackage.equals(packageName)) {
-                return;
-            }
-            unregisterAppOpChangeListener(this);
-            mActiveUnknownSourcesListeners.remove(this);
-            if (isDestroyed()) {
-                return;
-            }
-            new Handler(Looper.getMainLooper()).postDelayed(() -> {
-                if (!isDestroyed()) {
-                    // Relaunch Pia to continue installation.
-                    startActivity(getIntent()
-                        .putExtra(EXTRA_STAGED_SESSION_ID, mInstallViewModel.getStagedSessionId()));
-
-                    // If the userId of the root of activity stack is different from current userId,
-                    // starting Pia again lead to duplicate instances of the app in the stack.
-                    // As such, finish the old instance. Old Pia is finished even if the userId of
-                    // the root is the same, since there is no way to determine the difference in
-                    // userIds.
-                    finish();
-                }
-            }, 500);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
new file mode 100644
index 0000000..2b610d7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Process
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import android.view.Window
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.R
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallAborted
+import com.android.packageinstaller.v2.model.InstallFailed
+import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallSuccess
+import com.android.packageinstaller.v2.model.InstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment
+import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
+import com.android.packageinstaller.v2.viewmodel.InstallViewModel
+import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
+
+class InstallLaunch : FragmentActivity(), InstallActionListener {
+
+    companion object {
+        @JvmField val EXTRA_CALLING_PKG_UID =
+            InstallLaunch::class.java.packageName + ".callingPkgUid"
+        @JvmField val EXTRA_CALLING_PKG_NAME =
+            InstallLaunch::class.java.packageName + ".callingPkgName"
+        private val LOG_TAG = InstallLaunch::class.java.simpleName
+        private const val TAG_DIALOG = "dialog"
+    }
+
+    private val localLOGV = false
+
+    /**
+     * A collection of unknown sources listeners that are actively listening for app ops mode
+     * changes
+     */
+    private val activeUnknownSourcesListeners: MutableList<UnknownSourcesListener> = ArrayList(1)
+    private var installViewModel: InstallViewModel? = null
+    private var installRepository: InstallRepository? = null
+    private var fragmentManager: FragmentManager? = null
+    private var appOpsManager: AppOpsManager? = null
+    private lateinit var unknownAppsIntentLauncher: ActivityResultLauncher<Intent>
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        requestWindowFeature(Window.FEATURE_NO_TITLE)
+        fragmentManager = supportFragmentManager
+        appOpsManager = getSystemService(AppOpsManager::class.java)
+        installRepository = InstallRepository(applicationContext)
+        installViewModel = ViewModelProvider(
+            this, InstallViewModelFactory(this.application, installRepository!!)
+        )[InstallViewModel::class.java]
+
+        val intent = intent
+        val info = InstallRepository.CallerInfo(
+            intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
+            intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+        )
+        installViewModel!!.preprocessIntent(intent, info)
+        installViewModel!!.currentInstallStage.observe(this) { installStage: InstallStage ->
+            onInstallStageChange(installStage)
+        }
+
+        // Used to launch intent for Settings, to manage "install unknown apps" permission
+        unknownAppsIntentLauncher =
+            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+                // Reattempt installation on coming back from Settings, after toggling
+                // "install unknown apps" permission
+                installViewModel!!.reattemptInstall()
+            }
+    }
+
+    /**
+     * Main controller of the UI. This method shows relevant dialogs based on the install stage
+     */
+    private fun onInstallStageChange(installStage: InstallStage) {
+        when (installStage.stageCode) {
+            InstallStage.STAGE_STAGING -> {
+                val stagingDialog = InstallStagingFragment()
+                showDialogInner(stagingDialog)
+                installViewModel!!.stagingProgress.observe(this) { progress: Int ->
+                    stagingDialog.setProgress(progress)
+                }
+            }
+
+            InstallStage.STAGE_ABORTED -> {
+                val aborted = installStage as InstallAborted
+                when (aborted.abortReason) {
+                    InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
+                        setResult(aborted.activityResultCode, aborted.resultIntent, true)
+
+                    InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
+                    else -> setResult(Activity.RESULT_CANCELED, null, true)
+                }
+            }
+
+            InstallStage.STAGE_USER_ACTION_REQUIRED -> {
+                val uar = installStage as InstallUserActionRequired
+                when (uar.actionReason) {
+                    InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
+                        val actionDialog = InstallConfirmationFragment(uar)
+                        showDialogInner(actionDialog)
+                    }
+
+                    InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
+                        val externalSourceDialog = ExternalSourcesBlockedFragment(uar)
+                        showDialogInner(externalSourceDialog)
+                    }
+
+                    InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
+                        val anonymousSourceDialog = AnonymousSourceFragment()
+                        showDialogInner(anonymousSourceDialog)
+                    }
+                }
+            }
+
+            InstallStage.STAGE_INSTALLING -> {
+                val installing = installStage as InstallInstalling
+                val installingDialog = InstallInstallingFragment(installing)
+                showDialogInner(installingDialog)
+            }
+
+            InstallStage.STAGE_SUCCESS -> {
+                val success = installStage as InstallSuccess
+                if (success.shouldReturnResult) {
+                    val successIntent = success.resultIntent
+                    setResult(Activity.RESULT_OK, successIntent, true)
+                } else {
+                    val successFragment = InstallSuccessFragment(success)
+                    showDialogInner(successFragment)
+                }
+            }
+
+            InstallStage.STAGE_FAILED -> {
+                val failed = installStage as InstallFailed
+                val failedDialog = InstallFailedFragment(failed)
+                showDialogInner(failedDialog)
+            }
+
+            else -> {
+                Log.d(LOG_TAG, "Unimplemented stage: " + installStage.stageCode)
+                showDialogInner(null)
+            }
+        }
+    }
+
+    private fun showPolicyRestrictionDialog(aborted: InstallAborted) {
+        val restriction = aborted.message
+        val adminSupportIntent = aborted.resultIntent
+        var shouldFinish: Boolean = false
+
+        // If the given restriction is set by an admin, display information about the
+        // admin enforcing the restriction for the affected user. If not enforced by the admin,
+        // show the system dialog.
+        if (adminSupportIntent != null) {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "Restriction set by admin, starting $adminSupportIntent")
+            }
+            startActivity(adminSupportIntent)
+            // Finish the package installer app since the next dialog will not be shown by this app
+            shouldFinish = true
+        } else {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "Restriction set by system: $restriction")
+            }
+            val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
+            // Don't finish the package installer app since the next dialog
+            // will be shown by this app
+            shouldFinish = blockedByPolicyDialog != null
+            showDialogInner(blockedByPolicyDialog)
+        }
+        setResult(Activity.RESULT_CANCELED, null, shouldFinish)
+    }
+
+    /**
+     * Create a new dialog based on the install restriction enforced.
+     *
+     * @param restriction The restriction to create the dialog for
+     * @return The dialog
+     */
+    private fun createDevicePolicyRestrictionDialog(restriction: String?): DialogFragment? {
+        if (localLOGV) {
+            Log.i(LOG_TAG, "createDialog($restriction)")
+        }
+        return when (restriction) {
+            UserManager.DISALLOW_INSTALL_APPS ->
+                SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text)
+
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
+                SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text)
+
+            else -> null
+        }
+    }
+
+    /**
+     * Replace any visible dialog by the dialog returned by InstallRepository
+     *
+     * @param newDialog The new dialog to display
+     */
+    private fun showDialogInner(newDialog: DialogFragment?) {
+        val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+        currentDialog?.dismissAllowingStateLoss()
+        newDialog?.show(fragmentManager!!, TAG_DIALOG)
+    }
+
+    fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+        super.setResult(resultCode, data)
+        if (shouldFinish) {
+            finish()
+        }
+    }
+
+    override fun onPositiveResponse(reasonCode: Int) {
+        when (reasonCode) {
+            InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
+                installViewModel!!.forcedSkipSourceCheck()
+
+            InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
+                installViewModel!!.initiateInstall()
+        }
+    }
+
+    override fun onNegativeResponse(stageCode: Int) {
+        if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
+            installViewModel!!.cleanupInstall()
+        }
+        setResult(Activity.RESULT_CANCELED, null, true)
+    }
+
+    override fun sendUnknownAppsIntent(sourcePackageName: String) {
+        val settingsIntent = Intent()
+        settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+        val packageUri = Uri.parse("package:$sourcePackageName")
+        settingsIntent.setData(packageUri)
+        settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+        try {
+            registerAppOpChangeListener(
+                UnknownSourcesListener(sourcePackageName), sourcePackageName
+            )
+            unknownAppsIntentLauncher.launch(settingsIntent)
+        } catch (exc: ActivityNotFoundException) {
+            Log.e(
+                LOG_TAG, "Settings activity not found for action: "
+                    + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
+            )
+        }
+    }
+
+    override fun openInstalledApp(intent: Intent?) {
+        setResult(Activity.RESULT_OK, intent, true)
+        if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
+            startActivity(intent)
+        }
+    }
+
+    private fun registerAppOpChangeListener(listener: UnknownSourcesListener, packageName: String) {
+        appOpsManager!!.startWatchingMode(
+            AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES,
+            packageName,
+            listener
+        )
+        activeUnknownSourcesListeners.add(listener)
+    }
+
+    private fun unregisterAppOpChangeListener(listener: UnknownSourcesListener) {
+        activeUnknownSourcesListeners.remove(listener)
+        appOpsManager!!.stopWatchingMode(listener)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        while (activeUnknownSourcesListeners.isNotEmpty()) {
+            unregisterAppOpChangeListener(activeUnknownSourcesListeners[0])
+        }
+    }
+
+    private inner class UnknownSourcesListener(private val mOriginatingPackage: String) :
+        AppOpsManager.OnOpChangedListener {
+        override fun onOpChanged(op: String, packageName: String) {
+            if (mOriginatingPackage != packageName) {
+                return
+            }
+            unregisterAppOpChangeListener(this)
+            activeUnknownSourcesListeners.remove(this)
+            if (isDestroyed) {
+                return
+            }
+            Handler(Looper.getMainLooper()).postDelayed({
+                if (!isDestroyed) {
+                    // Relaunch Pia to continue installation.
+                    startActivity(
+                        intent.putExtra(
+                            InstallRepository.EXTRA_STAGED_SESSION_ID,
+                            installViewModel!!.stagedSessionId
+                        )
+                    )
+
+                    // If the userId of the root of activity stack is different from current userId,
+                    // starting Pia again lead to duplicate instances of the app in the stack.
+                    // As such, finish the old instance. Old Pia is finished even if the userId of
+                    // the root is the same, since there is no way to determine the difference in
+                    // userIds.
+                    finish()
+                }
+            }, 500)
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
similarity index 78%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
rename to packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
index b8a9355..33f5db3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.ui;
+package com.android.packageinstaller.v2.ui
 
-public interface UninstallActionListener {
-
-    void onPositiveResponse(boolean keepData);
-
-    void onNegativeResponse();
+interface UninstallActionListener {
+    fun onPositiveResponse(keepData: Boolean)
+    fun onNegativeResponse()
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
deleted file mode 100644
index 7638e91..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.ui;
-
-import static android.os.Process.INVALID_UID;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
-
-public class UninstallLaunch extends FragmentActivity implements UninstallActionListener {
-
-    public static final String EXTRA_CALLING_PKG_UID =
-        UninstallLaunch.class.getPackageName() + ".callingPkgUid";
-    public static final String EXTRA_CALLING_ACTIVITY_NAME =
-        UninstallLaunch.class.getPackageName() + ".callingActivityName";
-    public static final String TAG = UninstallLaunch.class.getSimpleName();
-    private static final String TAG_DIALOG = "dialog";
-
-    private UninstallViewModel mUninstallViewModel;
-    private UninstallRepository mUninstallRepository;
-    private FragmentManager mFragmentManager;
-    private NotificationManager mNotificationManager;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
-        // Never restore any state, esp. never create any fragments. The data in the fragment might
-        // be stale, if e.g. the app was uninstalled while the activity was destroyed.
-        super.onCreate(null);
-
-        mFragmentManager = getSupportFragmentManager();
-        mNotificationManager = getSystemService(NotificationManager.class);
-
-        mUninstallRepository = new UninstallRepository(getApplicationContext());
-        mUninstallViewModel = new ViewModelProvider(this,
-            new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
-            UninstallViewModel.class);
-
-        Intent intent = getIntent();
-        CallerInfo callerInfo = new CallerInfo(
-            intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
-            intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
-        mUninstallViewModel.preprocessIntent(intent, callerInfo);
-
-        mUninstallViewModel.getCurrentUninstallStage().observe(this,
-            this::onUninstallStageChange);
-    }
-
-    /**
-     * Main controller of the UI. This method shows relevant dialogs / fragments based on the
-     * uninstall stage
-     */
-    private void onUninstallStageChange(UninstallStage uninstallStage) {
-        if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) {
-            UninstallAborted aborted = (UninstallAborted) uninstallStage;
-            if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
-                aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) {
-                UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted);
-                showDialogInner(errorDialog);
-            } else {
-                setResult(aborted.getActivityResultCode(), null, true);
-            }
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_USER_ACTION_REQUIRED) {
-            UninstallUserActionRequired uar = (UninstallUserActionRequired) uninstallStage;
-            UninstallConfirmationFragment confirmationDialog = new UninstallConfirmationFragment(
-                uar);
-            showDialogInner(confirmationDialog);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_UNINSTALLING) {
-            // TODO: This shows a fragment whether or not user requests a result or not.
-            //  Originally, if the user does not request a result, we used to show a notification.
-            //  And a fragment if the user requests a result back. Should we consolidate and
-            //  show a fragment always?
-            UninstallUninstalling uninstalling = (UninstallUninstalling) uninstallStage;
-            UninstallUninstallingFragment uninstallingDialog = new UninstallUninstallingFragment(
-                uninstalling);
-            showDialogInner(uninstallingDialog);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_FAILED) {
-            UninstallFailed failed = (UninstallFailed) uninstallStage;
-            if (!failed.returnResult()) {
-                mNotificationManager.notify(failed.getUninstallId(),
-                    failed.getUninstallNotification());
-            }
-            setResult(failed.getActivityResultCode(), failed.getResultIntent(), true);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_SUCCESS) {
-            UninstallSuccess success = (UninstallSuccess) uninstallStage;
-            if (success.getMessage() != null) {
-                Toast.makeText(this, success.getMessage(), Toast.LENGTH_LONG).show();
-            }
-            setResult(success.getActivityResultCode(), success.getResultIntent(), true);
-        } else {
-            Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode());
-            showDialogInner(null);
-        }
-    }
-
-    /**
-     * Replace any visible dialog by the dialog returned by InstallRepository
-     *
-     * @param newDialog The new dialog to display
-     */
-    private void showDialogInner(DialogFragment newDialog) {
-        DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
-            TAG_DIALOG);
-        if (currentDialog != null) {
-            currentDialog.dismissAllowingStateLoss();
-        }
-        if (newDialog != null) {
-            newDialog.show(mFragmentManager, TAG_DIALOG);
-        }
-    }
-
-    public void setResult(int resultCode, Intent data, boolean shouldFinish) {
-        super.setResult(resultCode, data);
-        if (shouldFinish) {
-            finish();
-        }
-    }
-
-    @Override
-    public void onPositiveResponse(boolean keepData) {
-        mUninstallViewModel.initiateUninstall(keepData);
-    }
-
-    @Override
-    public void onNegativeResponse() {
-        mUninstallViewModel.cancelInstall();
-        setResult(Activity.RESULT_FIRST_USER, null, true);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
new file mode 100644
index 0000000..0050c7e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Bundle
+import android.os.Process
+import android.util.Log
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallAborted
+import com.android.packageinstaller.v2.model.UninstallFailed
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+import com.android.packageinstaller.v2.model.UninstallSuccess
+import com.android.packageinstaller.v2.model.UninstallUninstalling
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModel
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory
+
+class UninstallLaunch : FragmentActivity(), UninstallActionListener {
+
+    companion object {
+        @JvmField val EXTRA_CALLING_PKG_UID =
+            UninstallLaunch::class.java.packageName + ".callingPkgUid"
+        @JvmField val EXTRA_CALLING_ACTIVITY_NAME =
+            UninstallLaunch::class.java.packageName + ".callingActivityName"
+        val LOG_TAG = UninstallLaunch::class.java.simpleName
+        private const val TAG_DIALOG = "dialog"
+    }
+
+    private var uninstallViewModel: UninstallViewModel? = null
+    private var uninstallRepository: UninstallRepository? = null
+    private var fragmentManager: FragmentManager? = null
+    private var notificationManager: NotificationManager? = null
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+        // Never restore any state, esp. never create any fragments. The data in the fragment might
+        // be stale, if e.g. the app was uninstalled while the activity was destroyed.
+        super.onCreate(null)
+        fragmentManager = supportFragmentManager
+        notificationManager = getSystemService(NotificationManager::class.java)
+
+        uninstallRepository = UninstallRepository(applicationContext)
+        uninstallViewModel = ViewModelProvider(
+            this, UninstallViewModelFactory(this.application, uninstallRepository!!)
+        ).get(UninstallViewModel::class.java)
+
+        val intent = intent
+        val callerInfo = UninstallRepository.CallerInfo(
+            intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
+            intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+        )
+        uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+        uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage ->
+            onUninstallStageChange(uninstallStage)
+        }
+    }
+
+    /**
+     * Main controller of the UI. This method shows relevant dialogs / fragments based on the
+     * uninstall stage
+     */
+    private fun onUninstallStageChange(uninstallStage: UninstallStage) {
+        when (uninstallStage.stageCode) {
+            UninstallStage.STAGE_ABORTED -> {
+                val aborted = uninstallStage as UninstallAborted
+                if (aborted.abortReason == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
+                    aborted.abortReason == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED
+                ) {
+                    val errorDialog = UninstallErrorFragment(aborted)
+                    showDialogInner(errorDialog)
+                } else {
+                    setResult(aborted.activityResultCode, null, true)
+                }
+            }
+
+            UninstallStage.STAGE_USER_ACTION_REQUIRED -> {
+                val uar = uninstallStage as UninstallUserActionRequired
+                val confirmationDialog = UninstallConfirmationFragment(uar)
+                showDialogInner(confirmationDialog)
+            }
+
+            UninstallStage.STAGE_UNINSTALLING -> {
+                // TODO: This shows a fragment whether or not user requests a result or not.
+                //  Originally, if the user does not request a result, we used to show a notification.
+                //  And a fragment if the user requests a result back. Should we consolidate and
+                //  show a fragment always?
+                val uninstalling = uninstallStage as UninstallUninstalling
+                val uninstallingDialog = UninstallUninstallingFragment(uninstalling)
+                showDialogInner(uninstallingDialog)
+            }
+
+            UninstallStage.STAGE_FAILED -> {
+                val failed = uninstallStage as UninstallFailed
+                if (!failed.returnResult) {
+                    notificationManager!!.notify(
+                        failed.uninstallNotificationId!!, failed.uninstallNotification
+                    )
+                }
+                setResult(failed.activityResultCode, failed.resultIntent, true)
+            }
+
+            UninstallStage.STAGE_SUCCESS -> {
+                val success = uninstallStage as UninstallSuccess
+                if (success.message != null) {
+                    Toast.makeText(this, success.message, Toast.LENGTH_LONG).show()
+                }
+                setResult(success.activityResultCode, success.resultIntent, true)
+            }
+
+            else -> {
+                Log.e(LOG_TAG, "Invalid stage: " + uninstallStage.stageCode)
+                showDialogInner(null)
+            }
+        }
+    }
+
+    /**
+     * Replace any visible dialog by the dialog returned by InstallRepository
+     *
+     * @param newDialog The new dialog to display
+     */
+    private fun showDialogInner(newDialog: DialogFragment?) {
+        val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+        currentDialog?.dismissAllowingStateLoss()
+        newDialog?.show(fragmentManager!!, TAG_DIALOG)
+    }
+
+    fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+        super.setResult(resultCode, data)
+        if (shouldFinish) {
+            finish()
+        }
+    }
+
+    override fun onPositiveResponse(keepData: Boolean) {
+        uninstallViewModel!!.initiateUninstall(keepData)
+    }
+
+    override fun onNegativeResponse() {
+        uninstallViewModel!!.cancelInstall()
+        setResult(Activity.RESULT_FIRST_USER, null, true)
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
index 6d6fcc9..679f696 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -24,8 +24,8 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index 4cdce52..49901de 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 6398aef..25363d0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -28,7 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
index d45cd76..4667a7a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
@@ -28,7 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
+import com.android.packageinstaller.v2.model.InstallFailed;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
index 9f60f96..7327b5d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
+import com.android.packageinstaller.v2.model.InstallInstalling;
 
 /**
  * Dialog to show when an install is in progress.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
index ab6a932..b2a65faa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -29,8 +29,8 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallSuccess;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 import java.util.List;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
index 47fd67f..58b8b2d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
@@ -24,7 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.InstallStage;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 public class SimpleErrorFragment extends DialogFragment {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
index 1b0885e..32ac4a6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -30,7 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.UninstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
index 305daba..eb7183d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.model.UninstallAborted;
 import com.android.packageinstaller.v2.ui.UninstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
index 23cc421..835efc6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
@@ -22,7 +22,7 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
+import com.android.packageinstaller.v2.model.UninstallUninstalling;
 
 /**
  * Dialog to show that the app is uninstalling.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
deleted file mode 100644
index 04a0622..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-
-
-public class InstallViewModel extends AndroidViewModel {
-
-    private static final String TAG = InstallViewModel.class.getSimpleName();
-    private final InstallRepository mRepository;
-    private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>(
-            new InstallStaging());
-
-    public InstallViewModel(@NonNull Application application, InstallRepository repository) {
-        super(application);
-        mRepository = repository;
-    }
-
-    public MutableLiveData<InstallStage> getCurrentInstallStage() {
-        return mCurrentInstallStage;
-    }
-
-    public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
-        InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo);
-        if (stage.getStageCode() == InstallStage.STAGE_ABORTED) {
-            mCurrentInstallStage.setValue(stage);
-        } else {
-            // Since staging is an async operation, we will get the staging result later in time.
-            // Result of the file staging will be set in InstallRepository#mStagingResult.
-            // As such, mCurrentInstallStage will need to add another MutableLiveData
-            // as a data source
-            mRepository.stageForInstall();
-            mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> {
-                if (installStage.getStageCode() != InstallStage.STAGE_READY) {
-                    mCurrentInstallStage.setValue(installStage);
-                } else {
-                    checkIfAllowedAndInitiateInstall();
-                }
-            });
-        }
-    }
-
-    public MutableLiveData<Integer> getStagingProgress() {
-        return mRepository.getStagingProgress();
-    }
-
-    private void checkIfAllowedAndInitiateInstall() {
-        InstallStage stage = mRepository.requestUserConfirmation();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void forcedSkipSourceCheck() {
-        InstallStage stage = mRepository.forcedSkipSourceCheck();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void cleanupInstall() {
-        mRepository.cleanupInstall();
-    }
-
-    public void reattemptInstall() {
-        InstallStage stage = mRepository.reattemptInstall();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void initiateInstall() {
-        // Since installing is an async operation, we will get the install result later in time.
-        // Result of the installation will be set in InstallRepository#mInstallResult.
-        // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
-        mRepository.initiateInstall();
-        mCurrentInstallStage.addSource(mRepository.getInstallResult(), installStage -> {
-            if (installStage != null) {
-                mCurrentInstallStage.setValue(installStage);
-            }
-        });
-    }
-
-    public int getStagedSessionId() {
-        return mRepository.getStagedSessionId();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
new file mode 100644
index 0000000..072fb2d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallStaging
+
+class InstallViewModel(application: Application, val repository: InstallRepository) :
+    AndroidViewModel(application) {
+
+    companion object {
+        private val LOG_TAG = InstallViewModel::class.java.simpleName
+    }
+
+    private val _currentInstallStage = MediatorLiveData<InstallStage>(InstallStaging())
+    val currentInstallStage: MutableLiveData<InstallStage>
+        get() = _currentInstallStage
+
+    fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
+        val stage = repository.performPreInstallChecks(intent, callerInfo)
+        if (stage.stageCode == InstallStage.STAGE_ABORTED) {
+            _currentInstallStage.value = stage
+        } else {
+            // Since staging is an async operation, we will get the staging result later in time.
+            // Result of the file staging will be set in InstallRepository#mStagingResult.
+            // As such, mCurrentInstallStage will need to add another MutableLiveData
+            // as a data source
+            repository.stageForInstall()
+            _currentInstallStage.addSource(repository.stagingResult) { installStage: InstallStage ->
+                if (installStage.stageCode != InstallStage.STAGE_READY) {
+                    _currentInstallStage.value = installStage
+                } else {
+                    checkIfAllowedAndInitiateInstall()
+                }
+            }
+        }
+    }
+
+    val stagingProgress: LiveData<Int>
+        get() = repository.stagingProgress
+
+    private fun checkIfAllowedAndInitiateInstall() {
+        val stage = repository.requestUserConfirmation()
+        _currentInstallStage.value = stage
+    }
+
+    fun forcedSkipSourceCheck() {
+        val stage = repository.forcedSkipSourceCheck()
+        _currentInstallStage.value = stage
+    }
+
+    fun cleanupInstall() {
+        repository.cleanupInstall()
+    }
+
+    fun reattemptInstall() {
+        val stage = repository.reattemptInstall()
+        _currentInstallStage.value = stage
+    }
+
+    fun initiateInstall() {
+        // Since installing is an async operation, we will get the install result later in time.
+        // Result of the installation will be set in InstallRepository#mInstallResult.
+        // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
+        repository.initiateInstall()
+        _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
+            if (installStage != null) {
+                _currentInstallStage.value = installStage
+            }
+        }
+    }
+
+    val stagedSessionId: Int
+        get() = repository.stagedSessionId
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
deleted file mode 100644
index ef459e6..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.InstallRepository;
-
-public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
-    private final InstallRepository mRepository;
-    private final Application mApplication;
-
-    public InstallViewModelFactory(Application application, InstallRepository repository) {
-        // Calling super class' ctor ensures that create method is called correctly and the right
-        // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
-        // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
-        super(application);
-        mApplication = application;
-        mRepository = repository;
-    }
-
-    @NonNull
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-        return (T) new InstallViewModel(mApplication, mRepository);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
new file mode 100644
index 0000000..07b2f4f
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.InstallRepository
+
+class InstallViewModelFactory(val application: Application, val repository: InstallRepository) :
+    ViewModelProvider.AndroidViewModelFactory(application) {
+
+    // Calling super class' ctor ensures that create method is called correctly and the right
+    // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
+    // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return InstallViewModel(application, repository) as T
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
deleted file mode 100644
index 3f7bce8..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-
-public class UninstallViewModel extends AndroidViewModel {
-
-    private static final String TAG = UninstallViewModel.class.getSimpleName();
-    private final UninstallRepository mRepository;
-    private final MediatorLiveData<UninstallStage> mCurrentUninstallStage =
-        new MediatorLiveData<>();
-
-    public UninstallViewModel(@NonNull Application application, UninstallRepository repository) {
-        super(application);
-        mRepository = repository;
-    }
-
-    public MutableLiveData<UninstallStage> getCurrentUninstallStage() {
-        return mCurrentUninstallStage;
-    }
-
-    public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
-        UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
-        if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) {
-            stage = mRepository.generateUninstallDetails();
-        }
-        mCurrentUninstallStage.setValue(stage);
-    }
-
-    public void initiateUninstall(boolean keepData) {
-        mRepository.initiateUninstall(keepData);
-        // Since uninstall is an async operation, we will get the uninstall result later in time.
-        // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
-        // As such, mCurrentUninstallStage will need to add another MutableLiveData
-        // as a data source
-        mCurrentUninstallStage.addSource(mRepository.getUninstallResult(), uninstallStage -> {
-            if (uninstallStage != null) {
-                mCurrentUninstallStage.setValue(uninstallStage);
-            }
-        });
-    }
-
-    public void cancelInstall() {
-        mRepository.cancelInstall();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
new file mode 100644
index 0000000..80886e9
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+
+class UninstallViewModel(application: Application, val repository: UninstallRepository) :
+    AndroidViewModel(application) {
+
+    companion object {
+        private val LOG_TAG = UninstallViewModel::class.java.simpleName
+    }
+
+    private val _currentUninstallStage = MediatorLiveData<UninstallStage>()
+    val currentUninstallStage: MutableLiveData<UninstallStage>
+        get() = _currentUninstallStage
+
+    fun preprocessIntent(intent: Intent, callerInfo: UninstallRepository.CallerInfo) {
+        var stage = repository.performPreUninstallChecks(intent, callerInfo)
+        if (stage.stageCode != UninstallStage.STAGE_ABORTED) {
+            stage = repository.generateUninstallDetails()
+        }
+        _currentUninstallStage.value = stage
+    }
+
+    fun initiateUninstall(keepData: Boolean) {
+        repository.initiateUninstall(keepData)
+        // Since uninstall is an async operation, we will get the uninstall result later in time.
+        // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
+        // As such, _currentUninstallStage will need to add another MutableLiveData
+        // as a data source
+        _currentUninstallStage.addSource(repository.uninstallResult) { uninstallStage: UninstallStage? ->
+            if (uninstallStage != null) {
+                _currentUninstallStage.value = uninstallStage
+            }
+        }
+    }
+
+    fun cancelInstall() {
+        repository.cancelInstall()
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
deleted file mode 100644
index cd9845e..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-
-public class UninstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
-    private final UninstallRepository mRepository;
-    private final Application mApplication;
-
-    public UninstallViewModelFactory(Application application, UninstallRepository repository) {
-        // Calling super class' ctor ensures that create method is called correctly and the right
-        // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
-        // UninstallViewModel(application) is used, and repository isn't initialized in
-        // the viewmodel
-        super(application);
-        mApplication = application;
-        mRepository = repository;
-    }
-
-    @NonNull
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-        return (T) new UninstallViewModel(mApplication, mRepository);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
new file mode 100644
index 0000000..0a316e7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallRepository
+
+class UninstallViewModelFactory(val application: Application, val repository: UninstallRepository) :
+    ViewModelProvider.AndroidViewModelFactory(application) {
+
+    // Calling super class' ctor ensures that create method is called correctly and the right
+    // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
+    // UninstallViewModel(application) is used, and repository isn't initialized in
+    // the viewmodel
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return UninstallViewModel(application, repository) as T
+    }
+}
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index df72a92..044ba87 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -15,9 +15,12 @@
     resource_dirs: ["res"],
 
     static_libs: [
-          "androidx.annotation_annotation",
-          "SettingsLibTile"
+        "androidx.annotation_annotation",
+        "SettingsLibTile",
     ],
 
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2501869..ffe3d1d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -60,6 +60,9 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
index 986baf7..bd0dbdc 100644
--- a/packages/SettingsLib/EmergencyNumber/Android.bp
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -17,4 +17,7 @@
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce..d9f74da 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,4 +28,7 @@
         "com.android.extservices",
         "com.android.healthfitness",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 028489d..3b04bd9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -30,4 +30,7 @@
         "//apex_available:platform",
         "com.android.permission",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 2d93e4e..22e4e94 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -14,9 +14,12 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-          "androidx.annotation_annotation",
+        "androidx.annotation_annotation",
     ],
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c07a802..c385d38 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -15,4 +15,7 @@
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 66bd6f5..d5cf1a35 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -26,6 +26,7 @@
 import androidx.compose.material.icons.outlined.PowerOff
 import androidx.compose.material.icons.outlined.Shield
 import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -78,24 +79,19 @@
                 imageVector = Icons.Outlined.WarningAmber,
                 buttons = listOf(
                     CardButton(text = "Action") {},
-                    CardButton(text = "Action", isMain = true) {},
-                )
+                ),
+                tintColor = MaterialTheme.colorScheme.error,
+                containerColor = MaterialTheme.colorScheme.errorContainer,
             )
         )
     }
 
     @Composable
     private fun SettingsCardWithoutIcon() {
-        var isVisible by rememberSaveable { mutableStateOf(true) }
         SettingsCard(
             CardModel(
                 title = stringResource(R.string.sample_title),
                 text = stringResource(R.string.sample_text),
-                isVisible = { isVisible },
-                onDismiss = { isVisible = false },
-                buttons = listOf(
-                    CardButton(text = "Action") {},
-                ),
             )
         )
     }
@@ -104,6 +100,7 @@
     fun SampleSettingsCollapsibleCard() {
         val context = LocalContext.current
         var isVisible0 by rememberSaveable { mutableStateOf(true) }
+        var isVisible1 by rememberSaveable { mutableStateOf(true) }
         val cards = remember {
             mutableStateListOf(
                 CardModel(
@@ -114,16 +111,17 @@
                     onDismiss = { isVisible0 = false },
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                    )
+                    ),
                 ),
                 CardModel(
                     title = context.getString(R.string.sample_title),
                     text = context.getString(R.string.sample_text),
                     imageVector = Icons.Outlined.Shield,
+                    isVisible = { isVisible1 },
+                    onDismiss = { isVisible1 = false },
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                        CardButton(text = "Main action", isMain = true) {},
-                    )
+                    ),
                 )
             )
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 993cb4a..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,7 +37,6 @@
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
-    val iconSmall = 16.dp
     val iconLarge = 48.dp
 
     /** The size when app icon is displayed in list. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b18a1bc..b2a8b87 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -16,11 +16,11 @@
 
 package com.android.settingslib.spa.widget.card
 
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 
 data class CardButton(
     val text: String,
-    val isMain: Boolean = false,
     val onClick: () -> Unit,
 )
 
@@ -38,4 +38,10 @@
     val onDismiss: (() -> Unit)? = null,
 
     val buttons: List<CardButton> = emptyList(),
+
+    /** If specified, this color will be used to tint the icon and the buttons. */
+    val tintColor: Color = Color.Unspecified,
+
+    /** If specified, this color will be used to tint the icon and the buttons. */
+    val containerColor: Color = Color.Unspecified,
 )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 7eec888..c7845fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -23,26 +23,26 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.WarningAmber
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
@@ -72,11 +72,14 @@
 }
 
 @Composable
-fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+fun SettingsCardContent(
+    containerColor: Color = Color.Unspecified,
+    content: @Composable ColumnScope.() -> Unit,
+) {
     Card(
         shape = CornerExtraSmall,
         colors = CardDefaults.cardColors(
-            containerColor = SettingsTheme.colorScheme.surface,
+            containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface },
         ),
         modifier = Modifier
             .fillMaxWidth()
@@ -95,37 +98,43 @@
 @Composable
 internal fun SettingsCardImpl(model: CardModel) {
     AnimatedVisibility(visible = model.isVisible()) {
-        SettingsCardContent {
+        SettingsCardContent(containerColor = model.containerColor) {
             Column(
-                modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+                modifier = Modifier.padding(
+                    horizontal = SettingsDimension.dialogItemPaddingHorizontal,
+                    vertical = SettingsDimension.itemPaddingAround,
+                ),
                 verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
             ) {
-                CardHeader(model.imageVector, model.onDismiss)
+                CardHeader(model.imageVector, model.tintColor, model.onDismiss)
                 SettingsTitle(model.title)
                 SettingsBody(model.text)
-                Buttons(model.buttons)
+                Buttons(model.buttons, model.tintColor)
             }
         }
     }
 }
 
 @Composable
-fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) {
+fun CardHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) {
+    if (imageVector != null || onDismiss != null) {
+        Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical))
+    }
     Row(Modifier.fillMaxWidth()) {
-        CardIcon(imageVector)
+        CardIcon(imageVector, iconColor)
         Spacer(modifier = Modifier.weight(1f))
         DismissButton(onDismiss)
     }
 }
 
 @Composable
-private fun CardIcon(imageVector: ImageVector?) {
+private fun CardIcon(imageVector: ImageVector?, color: Color) {
     if (imageVector != null) {
         Icon(
             imageVector = imageVector,
             contentDescription = null,
             modifier = Modifier.size(SettingsDimension.itemIconSize),
-            tint = MaterialTheme.colorScheme.primary,
+            tint = color.takeOrElse { MaterialTheme.colorScheme.primary },
         )
     }
 }
@@ -146,52 +155,35 @@
                 contentDescription = stringResource(
                     androidx.compose.material3.R.string.m3c_snackbar_dismiss
                 ),
-                modifier = Modifier.size(SettingsDimension.iconSmall),
+                modifier = Modifier.padding(SettingsDimension.paddingSmall),
             )
         }
     }
 }
 
 @Composable
-private fun Buttons(buttons: List<CardButton>) {
+private fun Buttons(buttons: List<CardButton>, color: Color) {
     if (buttons.isNotEmpty()) {
         Row(
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(top = SettingsDimension.itemPaddingAround),
+            modifier = Modifier.fillMaxWidth(),
             horizontalArrangement = Arrangement.spacedBy(
                 space = SettingsDimension.itemPaddingEnd,
                 alignment = Alignment.End,
             ),
         ) {
             for (button in buttons) {
-                Button(button)
+                Button(button, color)
             }
         }
+    } else {
+        Spacer(Modifier.height(SettingsDimension.itemPaddingAround))
     }
 }
 
 @Composable
-private fun Button(button: CardButton) {
-    if (button.isMain) {
-        Button(
-            onClick = button.onClick,
-            colors = ButtonDefaults.buttonColors(
-                containerColor = SettingsTheme.colorScheme.primaryContainer,
-            ),
-        ) {
-            Text(
-                text = button.text,
-                color = SettingsTheme.colorScheme.onPrimaryContainer,
-            )
-        }
-    } else {
-        OutlinedButton(onClick = button.onClick) {
-            Text(
-                text = button.text,
-                color = MaterialTheme.colorScheme.onSurface,
-            )
-        }
+private fun Button(button: CardButton, color: Color) {
+    TextButton(onClick = button.onClick) {
+        Text(text = button.text, color = color)
     }
 }
 
@@ -206,7 +198,6 @@
                 imageVector = Icons.Outlined.WarningAmber,
                 buttons = listOf(
                     CardButton(text = "Action") {},
-                    CardButton(text = "Action", isMain = true) {},
                 )
             )
         )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 6e36490..c34df65 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -141,7 +141,6 @@
                     imageVector = Icons.Outlined.Shield,
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                        CardButton(text = "Main action", isMain = true) {},
                     )
                 )
             )
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index eb9e329..19c59dd 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -14,8 +14,11 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-          "androidx.annotation_annotation",
+        "androidx.annotation_annotation",
     ],
 
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index c7ad24c..d5a56c8 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -31,4 +31,7 @@
         "com.android.healthfitness",
         "com.android.permission",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 202e7fe..3b14712 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,6 +12,9 @@
     visibility: ["//visibility:private"],
     srcs: ["interface-src/**/*.java"],
     host_supported: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
@@ -24,6 +27,9 @@
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_plugin {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
index c9512cd..8eaea0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -151,7 +151,9 @@
     private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
         boolean status = true;
         for (AudioProductStrategy strategy : strategies) {
-            status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+            if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) {
+                status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+            }
         }
 
         return status;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index d5b5af7..97bbf12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -81,14 +81,17 @@
 
     @Override
     protected void transferToRoute(@NonNull MediaRoute2Info route) {
-        mRouterManager.transfer(mPackageName, route);
+        // TODO: b/279555229 - provide real user handle of a caller.
+        mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
     }
 
     @Override
     protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
         final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
         if (info != null) {
-            mRouterManager.transfer(info, device.mRouteInfo);
+            // TODO: b/279555229 - provide real user handle and package name of a caller.
+            mRouterManager.transfer(
+                    info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
             return true;
         }
         return false;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
index 8b5ea30..c835244 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -18,8 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -79,6 +82,8 @@
         when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
                 new AudioDeviceInfo[]{mAudioDeviceInfo});
+        doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
+                any(AudioProductStrategy.class));
         when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
                 AudioManager.STREAM_MUSIC))
                 .thenReturn((new AudioAttributes.Builder()).build());
@@ -92,7 +97,10 @@
     }
 
     @Test
-    public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+    public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
+        when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
+                mHearingDeviceAttribute);
+
         mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
                 mHearingDeviceAttribute,
                 HearingAidAudioRoutingConstants.RoutingValue.AUTO);
@@ -101,6 +109,17 @@
     }
 
     @Test
+    public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() {
+        when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
+
+        mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+                mHearingDeviceAttribute,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+        verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
+    }
+
+    @Test
     public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
         mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
                 mHearingDeviceAttribute,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index e5dbe5f..d6e8d26 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -112,5 +112,7 @@
         Settings.Global.Wearable.SCREENSHOT_ENABLED,
         Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
         Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
+        Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+        Settings.Global.FORCE_ENABLE_PSS_PROFILING,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d9fe733..f8bdcf6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -209,6 +209,7 @@
         VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
 
         VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
@@ -446,5 +447,7 @@
         VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85e8769..6ad10cc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@
                     Settings.Global.DSRM_DURATION_MILLIS,
                     Settings.Global.DSRM_ENABLED_ACTIONS,
                     Settings.Global.MODE_RINGER,
+                    Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
                     Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
                     Settings.Global.MULTI_SIM_SMS_PROMPT,
                     Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 50ed7ab..7f16ca5 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,7 +23,8 @@
     default_visibility: [
         "//visibility:override",
         "//frameworks/base/packages/SystemUI:__subpackages__",
-        "//platform_testing:__subpackages__"
+        "//frameworks/libs/systemui/tracinglib:__subpackages__",
+        "//platform_testing:__subpackages__",
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 782b850..41d12dc 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -205,6 +205,13 @@
 }
 
 flag {
+   name: "pss_task_switcher"
+   namespace: "systemui"
+   description: "Enable the task switcher feature for partial screen sharing"
+   bug: "317208379"
+}
+
+flag {
    name: "rest_to_unlock"
    namespace: "systemui"
    description: "Require prolonged touch for fingerprint authentication"
@@ -276,3 +283,9 @@
     bug: "312784809"
 }
 
+flag {
+   name: "bluetooth_qs_tile_dialog_auto_on_toggle"
+   namespace: "systemui"
+   description: "Displays the auto on toggle in the bluetooth QS tile dialog"
+   bug: "316985153"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 2a9cf0f..55fc3a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,10 +20,12 @@
 import android.util.SizeF
 import android.widget.FrameLayout
 import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -31,11 +33,13 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Edit
@@ -98,7 +102,6 @@
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
         CommunalHubLazyGrid(
-            modifier = Modifier.align(Alignment.CenterStart),
             communalContent = communalContent,
             viewModel = viewModel,
             contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
@@ -138,21 +141,21 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
     communalContent: List<CommunalContentModel>,
     viewModel: BaseCommunalViewModel,
-    modifier: Modifier = Modifier,
     contentPadding: PaddingValues,
     setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
 ) {
-    var gridModifier = modifier
+    var gridModifier = Modifier.align(Alignment.CenterStart)
     val gridState = rememberLazyGridState()
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
         val contentListState = rememberContentListState(communalContent, viewModel)
         list = contentListState.list
+        // for drag & drop operations within the communal hub grid
         dragDropState =
             rememberGridDragDropState(
                 gridState = gridState,
@@ -164,9 +167,22 @@
                 .fillMaxSize()
                 .dragContainer(dragDropState, beforeContentPadding(contentPadding))
                 .onGloballyPositioned { setGridCoordinates(it) }
+        // for widgets dropped from other activities
+        val dragAndDropTargetState =
+            rememberDragAndDropTargetState(
+                gridState = gridState,
+                contentListState = contentListState,
+                updateDragPositionForRemove = updateDragPositionForRemove
+            )
+
+        // A full size box in background that listens to widget drops from the picker.
+        // Since the grid has its own listener for in-grid drag events, we use a separate element
+        // for android drag events.
+        Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
     } else {
         gridModifier = gridModifier.height(Dimensions.GridHeight)
     }
+
     LazyHorizontalGrid(
         modifier = gridModifier,
         state = gridState,
@@ -309,12 +325,24 @@
 ) {
     when (model) {
         is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+        is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
         is CommunalContentModel.Umo -> Umo(viewModel, modifier)
     }
 }
 
+/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+@Composable
+fun WidgetPlaceholderContent(size: SizeF) {
+    Card(
+        modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+        colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+        border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+        shape = RoundedCornerShape(16.dp)
+    ) {}
+}
+
 @Composable
 private fun WidgetContent(
     model: CommunalContentModel.Widget,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 89c5765..979991d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -16,11 +16,10 @@
 
 package com.android.systemui.communal.ui.compose
 
+import android.content.ComponentName
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 
@@ -32,6 +31,7 @@
     return remember(communalContent) {
         ContentListState(
             communalContent,
+            viewModel::onAddWidget,
             viewModel::onDeleteWidget,
             viewModel::onReorderWidgets,
         )
@@ -46,30 +46,57 @@
 class ContentListState
 internal constructor(
     communalContent: List<CommunalContentModel>,
+    private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
     private val onDeleteWidget: (id: Int) -> Unit,
-    private val onReorderWidgets: (ids: List<Int>) -> Unit,
+    private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
 ) {
-    var list by mutableStateOf(communalContent)
+    var list = communalContent.toMutableStateList()
         private set
 
     /** Move item to a new position in the list. */
     fun onMove(fromIndex: Int, toIndex: Int) {
-        list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
+        list.apply { add(toIndex, removeAt(fromIndex)) }
     }
 
     /** Remove widget from the list and the database. */
     fun onRemove(indexToRemove: Int) {
         if (list[indexToRemove] is CommunalContentModel.Widget) {
             val widget = list[indexToRemove] as CommunalContentModel.Widget
-            list = list.toMutableList().apply { removeAt(indexToRemove) }
+            list.apply { removeAt(indexToRemove) }
             onDeleteWidget(widget.appWidgetId)
         }
     }
 
-    /** Persist the new order with all the movements happened during dragging. */
-    fun onSaveList() {
-        val widgetIds: List<Int> =
-            list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId }
-        onReorderWidgets(widgetIds)
+    /**
+     * Persists the new order with all the movements happened during drag operations & the new
+     * widget drop (if applicable).
+     *
+     * @param newItemComponentName name of the new widget that was dropped into the list; null if no
+     *   new widget was added.
+     * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
+     *   widget was dropped.
+     */
+    fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
+        // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
+        // in the list. When persisted in DB, this leaves space for the new item (to be added) at
+        // the correct priority.
+        val widgetIdToPriorityMap: Map<Int, Int> =
+            list
+                .mapIndexedNotNull { index, item ->
+                    if (item is CommunalContentModel.Widget) {
+                        item.appWidgetId to list.size - index
+                    } else {
+                        null
+                    }
+                }
+                .toMap()
+        // reorder and then add the new widget
+        onReorderWidgets(widgetIdToPriorityMap)
+        if (newItemComponentName != null && newItemIndex != null) {
+            onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
+        }
     }
+
+    /** Returns true if the item at given index is editable. */
+    fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
new file mode 100644
index 0000000..22aa837
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.view.DragEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+/**
+ * Holds state associated with dragging and dropping items from other activities into the lazy grid.
+ *
+ * @see dragAndDropTarget
+ */
+@Composable
+internal fun rememberDragAndDropTargetState(
+    gridState: LazyGridState,
+    contentListState: ContentListState,
+    updateDragPositionForRemove: (offset: Offset) -> Boolean,
+): DragAndDropTargetState {
+    val scope = rememberCoroutineScope()
+    val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
+    // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
+    // that allows differentiating intention of scrolling from intention of dragging over the first
+    // visible item.
+    val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+    val state =
+        remember(gridState, contentListState) {
+            DragAndDropTargetState(
+                state = gridState,
+                contentListState = contentListState,
+                scope = scope,
+                autoScrollSpeed = autoScrollSpeed,
+                autoScrollThreshold = autoScrollThreshold,
+                updateDragPositionForRemove = updateDragPositionForRemove,
+            )
+        }
+    LaunchedEffect(autoScrollSpeed.floatValue) {
+        if (autoScrollSpeed.floatValue != 0f) {
+            while (isActive) {
+                gridState.scrollBy(autoScrollSpeed.floatValue)
+                delay(10)
+            }
+        }
+    }
+    return state
+}
+
+/**
+ * Attaches a listener for drag and drop events from other activities.
+ *
+ * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
+ * @see DragEvent
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun Modifier.dragAndDropTarget(
+    dragDropTargetState: DragAndDropTargetState,
+): Modifier {
+    val state by rememberUpdatedState(dragDropTargetState)
+
+    return this then
+        Modifier.dragAndDropTarget(
+            shouldStartDragAndDrop = accept@{ startEvent ->
+                    startEvent.mimeTypes().any { it == ClipDescription.MIMETYPE_TEXT_INTENT }
+                },
+            target =
+                object : DragAndDropTarget {
+                    override fun onStarted(event: DragAndDropEvent) {
+                        state.onStarted()
+                    }
+
+                    override fun onMoved(event: DragAndDropEvent) {
+                        state.onMoved(event)
+                    }
+
+                    override fun onDrop(event: DragAndDropEvent): Boolean {
+                        return state.onDrop(event)
+                    }
+
+                    override fun onEnded(event: DragAndDropEvent) {
+                        state.onEnded()
+                    }
+                }
+        )
+}
+
+/**
+ * Handles dropping of an item coming from a different activity (e.g. widget picker) in to the grid
+ * corresponding to the provided [LazyGridState].
+ *
+ * Adds a placeholder container to highlight the anticipated location the widget will be dropped to.
+ * When the item is held over an empty area, the placeholder appears at the end of the grid if one
+ * didn't exist already. As user moves the item over an existing item, the placeholder appears in
+ * place of that existing item. And then, the existing item is pushed over as part of re-ordering.
+ *
+ * Once item is dropped, new ordering along with the dropped item is persisted. See
+ * [ContentListState.onSaveList].
+ *
+ * Difference between this and [GridDragDropState] is that, this is used for listening to drops from
+ * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
+ * the communal hub grid.
+ */
+internal class DragAndDropTargetState(
+    private val state: LazyGridState,
+    private val contentListState: ContentListState,
+    private val scope: CoroutineScope,
+    private val autoScrollSpeed: MutableState<Float>,
+    private val autoScrollThreshold: Float,
+    private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+) {
+    /**
+     * The placeholder item that is treated as if it is being dragged across the grid. It is added
+     * to grid once drag and drop event is started and removed when event ends.
+     */
+    private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+
+    private var placeHolderIndex: Int? = null
+    private var isOnRemoveButton = false
+
+    fun onStarted() {
+        // assume item will be added to the end.
+        contentListState.list.add(placeHolder)
+        placeHolderIndex = contentListState.list.size - 1
+    }
+
+    fun onMoved(event: DragAndDropEvent) {
+        val dragEvent = event.toAndroidDragEvent()
+        isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
+        if (!isOnRemoveButton) {
+            findTargetItem(dragEvent)?.apply {
+                var scrollIndex: Int? = null
+                var scrollOffset: Int? = null
+                if (placeHolderIndex == state.firstVisibleItemIndex) {
+                    // Save info about the first item before the move, to neutralize the automatic
+                    // keeping first item first.
+                    scrollIndex = placeHolderIndex
+                    scrollOffset = state.firstVisibleItemScrollOffset
+                }
+
+                autoScrollIfNearEdges(dragEvent)
+
+                if (contentListState.isItemEditable(this.index)) {
+                    movePlaceholderTo(this.index)
+                    placeHolderIndex = this.index
+                }
+
+                if (scrollIndex != null && scrollOffset != null) {
+                    // this is needed to neutralize automatic keeping the first item first.
+                    scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+                }
+            }
+        }
+    }
+
+    fun onDrop(event: DragAndDropEvent): Boolean {
+        autoScrollSpeed.value = 0f
+        if (isOnRemoveButton) {
+            return false
+        }
+        return placeHolderIndex?.let { dropIndex ->
+            val componentName = event.maybeWidgetComponentName()
+            if (componentName != null) {
+                // Placeholder isn't removed yet to allow the setting the right priority for items
+                // before adding in the new item.
+                contentListState.onSaveList(
+                    newItemComponentName = componentName,
+                    newItemIndex = dropIndex
+                )
+                return@let true
+            }
+            return false
+        }
+            ?: false
+    }
+
+    fun onEnded() {
+        autoScrollSpeed.value = 0f
+        placeHolderIndex = null
+        contentListState.list.remove(placeHolder)
+        isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
+    }
+
+    private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+        val orientation = state.layoutInfo.orientation
+        val distanceFromStart =
+            if (orientation == Orientation.Horizontal) {
+                dragEvent.x
+            } else {
+                dragEvent.y
+            }
+        val distanceFromEnd =
+            if (orientation == Orientation.Horizontal) {
+                state.layoutInfo.viewportSize.width - dragEvent.x
+            } else {
+                state.layoutInfo.viewportSize.height - dragEvent.y
+            }
+        autoScrollSpeed.value =
+            when {
+                distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+                distanceFromStart < autoScrollThreshold ->
+                    -(autoScrollThreshold - distanceFromStart)
+                else -> 0f
+            }
+    }
+
+    private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
+        state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+            dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
+                dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
+        }
+
+    private fun movePlaceholderTo(index: Int) {
+        val currentIndex = contentListState.list.indexOf(placeHolder)
+        if (currentIndex != index) {
+            contentListState.onMove(currentIndex, index)
+        }
+    }
+
+    /**
+     * Parses and returns the component name of the widget that was dropped into the communal grid.
+     *
+     * Returns null if the drop event didn't include the widget information.
+     */
+    private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
+        val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+        return clipData
+            ?.getItemAt(0)
+            ?.intent
+            ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5451d05..0d460aa8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -32,15 +32,13 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
-import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
@@ -112,7 +110,7 @@
             .firstOrNull { item ->
                 // grid item offset is based off grid content container so we need to deduct
                 // before content padding from the initial pointer position
-                item.isEditable &&
+                contentListState.isItemEditable(item.index) &&
                     (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
                     (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
             }
@@ -149,7 +147,7 @@
 
         val targetItem =
             state.layoutInfo.visibleItemsInfo.find { item ->
-                item.isEditable &&
+                contentListState.isItemEditable(item.index) &&
                     middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
                     middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
                     draggingItem.index != item.index
@@ -187,10 +185,6 @@
     private val LazyGridItemInfo.offsetEnd: IntOffset
         get() = this.offset + this.size
 
-    /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */
-    private val LazyGridItemInfo.isEditable: Boolean
-        get() = contentListState.list[this.index] is CommunalContentModel.Widget
-
     /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */
     private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float {
         return when {
@@ -210,14 +204,6 @@
     }
 }
 
-private operator fun IntOffset.plus(size: IntSize): IntOffset {
-    return IntOffset(x + size.width, y + size.height)
-}
-
-private operator fun Offset.plus(size: Size): Offset {
-    return Offset(x + size.width, y + size.height)
-}
-
 fun Modifier.dragContainer(
     dragDropState: GridDragDropState,
     beforeContentPadding: ContentPaddingInPx
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
new file mode 100644
index 0000000..b86c07e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+/** Adds the given size to the x and y offsets in this [IntOffset] */
+operator fun IntOffset.plus(size: IntSize): IntOffset {
+    return IntOffset(x + size.width, y + size.height)
+}
+
+/** Adds the given size to the x and y offsets in this [Offset]. */
+operator fun Offset.plus(size: Size): Offset {
+    return Offset(x + size.width, y + size.height)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 0000000..472484a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+    viewModel: KeyguardLongPressViewModel,
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+    val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+    val interactionSource = remember { MutableInteractionSource() }
+
+    Box(
+        modifier =
+            modifier
+                .combinedClickable(
+                    enabled = isEnabled,
+                    onLongClick = viewModel::onLongPress,
+                    onClick = {},
+                    interactionSource = interactionSource,
+                    // Passing null for the indication removes the ripple effect.
+                    indication = null,
+                )
+                .pointerInput(settingsMenuBounds) {
+                    awaitEachGesture {
+                        val pointerInputChange = awaitFirstDown()
+                        if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+                            viewModel.onTouchedOutside()
+                        }
+                    }
+                },
+    ) {
+        content(setSettingsMenuBounds)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec..67a6820 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalFoundationApi::class)
-
 package com.android.systemui.keyguard.ui.composable
 
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: LockscreenSceneViewModel,
-    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
     private val lockscreenContent: Lazy<LockscreenContent>,
+    private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
@@ -93,9 +72,8 @@
         modifier: Modifier,
     ) {
         LockscreenScene(
-            viewProvider = viewProvider,
-            viewModel = viewModel,
             lockscreenContent = lockscreenContent,
+            viewBasedLockscreenContent = viewBasedLockscreenContent,
             modifier = modifier,
         )
     }
@@ -116,98 +94,21 @@
 
 @Composable
 private fun SceneScope.LockscreenScene(
-    viewProvider: () -> View,
-    viewModel: LockscreenSceneViewModel,
     lockscreenContent: Lazy<LockscreenContent>,
+    viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
-    fun findSettingsMenu(): View {
-        return viewProvider().requireViewById(R.id.keyguard_settings_button)
-    }
-
-    Box(
-        modifier = modifier,
-    ) {
-        LongPressSurface(
-            viewModel = viewModel.longPress,
-            isSettingsMenuVisible = { findSettingsMenu().isVisible },
-            settingsMenuBounds = {
-                val bounds = android.graphics.Rect()
-                findSettingsMenu().getHitRect(bounds)
-                bounds.toComposeRect()
-            },
-            modifier = Modifier.fillMaxSize(),
-        )
-
-        if (UseLockscreenContent) {
-            lockscreenContent
-                .get()
-                .Content(
-                    modifier = Modifier.fillMaxSize(),
-                )
-        } else {
-            AndroidView(
-                factory = { _ ->
-                    val keyguardRootView = viewProvider()
-                    // Remove the KeyguardRootView from any parent it might already have in legacy
-                    // code just in case (a view can't have two parents).
-                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
-                    keyguardRootView
-                },
-                modifier = Modifier.fillMaxSize(),
+    if (UseLockscreenContent) {
+        lockscreenContent
+            .get()
+            .Content(
+                modifier = modifier.fillMaxSize(),
+            )
+    } else {
+        with(viewBasedLockscreenContent.get()) {
+            Content(
+                modifier = modifier.fillMaxSize(),
             )
         }
-
-        val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
-        Layout(
-            modifier = Modifier.fillMaxSize(),
-            content = {
-                NotificationStack(
-                    viewModel = viewModel.notifications,
-                    isScrimVisible = false,
-                )
-            }
-        ) { measurables, constraints ->
-            check(measurables.size == 1)
-            val height = notificationStackPosition.height.toInt()
-            val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
-            val placeable = measurables[0].measure(childConstraints)
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                val start = (constraints.maxWidth - placeable.measuredWidth) / 2
-                placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
-            }
-        }
     }
 }
-
-@Composable
-private fun LongPressSurface(
-    viewModel: KeyguardLongPressViewModel,
-    isSettingsMenuVisible: () -> Boolean,
-    settingsMenuBounds: () -> Rect,
-    modifier: Modifier = Modifier,
-) {
-    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
-    Box(
-        modifier =
-            modifier
-                .combinedClickable(
-                    enabled = isEnabled,
-                    onLongClick = viewModel::onLongPress,
-                    onClick = {},
-                )
-                .pointerInput(Unit) {
-                    awaitEachGesture {
-                        val pointerInputChange = awaitFirstDown()
-                        if (
-                            isSettingsMenuVisible() &&
-                                !settingsMenuBounds().contains(pointerInputChange.position)
-                        ) {
-                            viewModel.onTouchedOutside()
-                        }
-                    }
-                },
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 0000000..976161b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+    private val viewModel: LockscreenSceneViewModel,
+    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+    @Composable
+    fun SceneScope.Content(
+        modifier: Modifier = Modifier,
+    ) {
+        fun findSettingsMenu(): View {
+            return viewProvider().requireViewById(R.id.keyguard_settings_button)
+        }
+
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { onSettingsMenuPlaced ->
+            AndroidView(
+                factory = { _ ->
+                    val keyguardRootView = viewProvider()
+                    // Remove the KeyguardRootView from any parent it might already have in legacy
+                    // code just in case (a view can't have two parents).
+                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+                    keyguardRootView
+                },
+                modifier = Modifier.fillMaxSize(),
+            )
+
+            val notificationStackPosition by
+                viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+            Layout(
+                modifier =
+                    Modifier.fillMaxSize().onPlaced {
+                        val settingsMenuView = findSettingsMenu()
+                        onSettingsMenuPlaced(
+                            if (settingsMenuView.isVisible) {
+                                val bounds = Rect()
+                                settingsMenuView.getHitRect(bounds)
+                                bounds.toComposeRect()
+                            } else {
+                                null
+                            }
+                        )
+                    },
+                content = {
+                    NotificationStack(
+                        viewModel = viewModel.notifications,
+                        isScrimVisible = false,
+                    )
+                }
+            ) { measurables, constraints ->
+                check(measurables.size == 1)
+                val height = notificationStackPosition.height.toInt()
+                val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+                val placeable = measurables[0].measure(childConstraints)
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+                    placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
new file mode 100644
index 0000000..efa8cc7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.ui.layout.HorizontalAlignmentLine
+import androidx.compose.ui.layout.VerticalAlignmentLine
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Encapsulates all blueprint alignment lines.
+ *
+ * These can be used to communicate alignment lines emitted by elements that the blueprint should
+ * consume and use to know how to constrain and/or place other elements in that blueprint.
+ *
+ * For more information, please see
+ * [the official documentation](https://developer.android.com/jetpack/compose/layouts/alignment-lines).
+ */
+object BlueprintAlignmentLines {
+
+    /**
+     * Encapsulates alignment lines produced by the lock icon element.
+     *
+     * Because the lock icon is also the same element as the under-display fingerprint sensor
+     * (UDFPS), blueprints should use its alignment lines to make sure that other elements on screen
+     * do not overlap with the lock icon.
+     */
+    object LockIcon {
+
+        /** The left edge of the lock icon. */
+        val Left =
+            VerticalAlignmentLine(
+                merger = { old, new ->
+                    // When two left alignment line values are provided, choose the leftmost one:
+                    min(old, new)
+                },
+            )
+
+        /** The top edge of the lock icon. */
+        val Top =
+            HorizontalAlignmentLine(
+                merger = { old, new ->
+                    // When two top alignment line values are provided, choose the topmost one:
+                    min(old, new)
+                },
+            )
+
+        /** The right edge of the lock icon. */
+        val Right =
+            VerticalAlignmentLine(
+                merger = { old, new ->
+                    // When two right alignment line values are provided, choose the rightmost one:
+                    max(old, new)
+                },
+            )
+
+        /** The bottom edge of the lock icon. */
+        val Bottom =
+            HorizontalAlignmentLine(
+                merger = { old, new ->
+                    // When two bottom alignment line values are provided, choose the bottommost
+                    // one:
+                    max(old, new)
+                },
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf..86124c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
 import javax.inject.Inject
 
 /** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "communal"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): communal blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): communal blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index fc1df84..d9d98cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -16,23 +16,21 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -56,53 +54,125 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "default"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        val context = LocalContext.current
-        val lockIconBounds = lockSection.lockIconBounds(context)
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Box(
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) {
-            Column(
-                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
-            ) {
-                with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                with(notificationSection) {
-                    Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                }
-                if (!isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
                     }
-                }
-            }
 
-            with(lockSection) {
-                LockIcon(
-                    modifier =
-                        Modifier.width { lockIconBounds.width() }
-                            .height { lockIconBounds.height() }
-                            .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) }
-                )
-            }
+                    with(lockSection) { LockIcon() }
 
-            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
-                if (isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
                     }
-                }
 
-                with(bottomAreaSection) { BottomArea(modifier = Modifier.fillMaxWidth()) }
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(bottomAreaSection) {
+                        Shortcut(isStart = true, applyPadding = true)
+                        Shortcut(isStart = false, applyPadding = true)
+                    }
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val belowLockIconMeasurable = measurables[2]
+                val startShortcutMeasurable = measurables[3]
+                val endShortcutMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    startShortcutPleaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - startShortcutPleaceable.height,
+                    )
+                    endShortcutPleaceable.place(
+                        x = constraints.maxWidth - endShortcutPleaceable.width,
+                        y = constraints.maxHeight - endShortcutPleaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index fa913f1..4704f5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -16,38 +16,28 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
 import javax.inject.Inject
-import kotlin.math.roundToInt
 
 /**
  * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -64,100 +54,130 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        val context = LocalContext.current
-        val lockIconBounds = lockSection.lockIconBounds(context)
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Box(
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) {
-            Column(
-                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
-            ) {
-                with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                with(notificationSection) {
-                    Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                }
-                if (!isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
-                    }
-                }
-            }
-
-            val shortcutSizePx =
-                with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() }
-
-            Row(
-                verticalAlignment = Alignment.CenterVertically,
-                modifier =
-                    Modifier.fillMaxWidth().offset {
-                        val rowTop =
-                            if (shortcutSizePx.height > lockIconBounds.height()) {
-                                (lockIconBounds.top -
-                                        (shortcutSizePx.height + lockIconBounds.height()) / 2)
-                                    .roundToInt()
-                            } else {
-                                lockIconBounds.top
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
                             }
-
-                        IntOffset(0, rowTop)
-                    },
-            ) {
-                Spacer(Modifier.weight(1f))
-
-                with(bottomAreaSection) { Shortcut(isStart = true) }
-
-                Spacer(Modifier.weight(1f))
-
-                with(lockSection) {
-                    LockIcon(
-                        modifier =
-                            Modifier.width { lockIconBounds.width() }
-                                .height { lockIconBounds.height() }
-                    )
-                }
-
-                Spacer(Modifier.weight(1f))
-
-                with(bottomAreaSection) { Shortcut(isStart = false) }
-
-                Spacer(Modifier.weight(1f))
-            }
-
-            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
-                if (isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
+                        }
                     }
-                }
 
-                with(bottomAreaSection) {
-                    IndicationArea(
-                        modifier =
-                            Modifier.fillMaxWidth()
-                                .padding(
-                                    horizontal =
-                                        dimensionResource(
-                                            R.dimen.keyguard_affordance_horizontal_offset
-                                        )
-                                )
-                                .padding(
-                                    bottom =
-                                        dimensionResource(
-                                            R.dimen.keyguard_affordance_vertical_offset
-                                        )
-                                )
-                                .heightIn(min = shortcutSizeDp().height),
+                    // Constrained to the left of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+
+                    with(lockSection) { LockIcon() }
+
+                    // Constrained to the right of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val startSideShortcutMeasurable = measurables[1]
+                val lockIconMeasurable = measurables[2]
+                val endSideShortcutMeasurable = measurables[3]
+                val belowLockIconMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val startSideShortcutPlaceable =
+                    startSideShortcutMeasurable.measure(noMinConstraints)
+                val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    startSideShortcutPlaceable.placeRelative(
+                        x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    endSideShortcutPlaceable.placeRelative(
+                        x =
+                            lockIconBounds.right +
+                                (constraints.maxWidth - lockIconBounds.right) / 2 -
+                                endSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
                     )
                 }
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5f..fdf1166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@
  * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
  * tablet form factor).
  */
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): split shade blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): split shade blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 53e4be3..db20f65 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import android.widget.ImageView
 import androidx.annotation.IdRes
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -58,38 +57,17 @@
     private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
     private val keyguardRootViewModel: KeyguardRootViewModel,
 ) {
-    @Composable
-    fun SceneScope.BottomArea(
-        modifier: Modifier = Modifier,
-    ) {
-        Row(
-            modifier =
-                modifier
-                    .padding(
-                        horizontal =
-                            dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
-                    )
-                    .padding(
-                        bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset)
-                    ),
-        ) {
-            Shortcut(
-                isStart = true,
-            )
-
-            IndicationArea(
-                modifier = Modifier.weight(1f),
-            )
-
-            Shortcut(
-                isStart = false,
-            )
-        }
-    }
-
+    /**
+     * Renders a single lockscreen shortcut.
+     *
+     * @param isStart Whether the shortcut goes on the left (in left-to-right locales).
+     * @param applyPadding Whether to apply padding around the shortcut, this is needed if the
+     *   shortcut is placed along the edges of the display.
+     */
     @Composable
     fun SceneScope.Shortcut(
         isStart: Boolean,
+        applyPadding: Boolean,
         modifier: Modifier = Modifier,
     ) {
         MovableElement(
@@ -103,6 +81,12 @@
                 falsingManager = falsingManager,
                 vibratorHelper = vibratorHelper,
                 indicationController = indicationController,
+                modifier =
+                    if (applyPadding) {
+                        Modifier.shortcutPadding()
+                    } else {
+                        Modifier
+                    }
             )
         }
     }
@@ -113,7 +97,7 @@
     ) {
         MovableElement(
             key = IndicationAreaElementKey,
-            modifier = modifier,
+            modifier = modifier.shortcutPadding(),
         ) {
             IndicationArea(
                 indicationAreaViewModel = indicationAreaViewModel,
@@ -218,6 +202,14 @@
             modifier = modifier.fillMaxWidth(),
         )
     }
+
+    @Composable
+    private fun Modifier.shortcutPadding(): Modifier {
+        return this.padding(
+                horizontal = dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+            )
+            .padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
+    }
 }
 
 private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 8bbe424b..2a6bea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -17,23 +17,35 @@
 package com.android.systemui.keyguard.ui.composable.section
 
 import android.content.Context
-import android.graphics.Point
-import android.graphics.Rect
 import android.util.DisplayMetrics
 import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
 import javax.inject.Inject
 
 class LockSection
@@ -42,23 +54,70 @@
     private val windowManager: WindowManager,
     private val authController: AuthController,
     private val featureFlags: FeatureFlagsClassic,
+    private val lockIconViewController: Lazy<LockIconViewController>,
+    private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+    private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+    private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+    private val falsingManager: Lazy<FalsingManager>,
+    private val vibratorHelper: Lazy<VibratorHelper>,
 ) {
     @Composable
     fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = LockIconElementKey,
-            modifier = modifier,
-        ) {
-            Box(
-                modifier = Modifier.background(Color.Red),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Lock",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
+        if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+            return
         }
+
+        val context = LocalContext.current
+
+        AndroidView(
+            factory = { context ->
+                val view =
+                    if (DeviceEntryUdfpsRefactor.isEnabled) {
+                        DeviceEntryIconView(context, null).apply {
+                            id = R.id.device_entry_icon_view
+                            DeviceEntryIconViewBinder.bind(
+                                this,
+                                deviceEntryIconViewModel.get(),
+                                deviceEntryForegroundViewModel.get(),
+                                deviceEntryBackgroundViewModel.get(),
+                                falsingManager.get(),
+                                vibratorHelper.get(),
+                            )
+                        }
+                    } else {
+                        // keyguardBottomAreaRefactor()
+                        LockIconView(context, null).apply {
+                            id = R.id.lock_icon_view
+                            lockIconViewController.get().setLockIconView(this)
+                        }
+                    }
+                view
+            },
+            modifier =
+                modifier.element(LockIconElementKey).layout { measurable, _ ->
+                    val lockIconBounds = lockIconBounds(context)
+                    val placeable =
+                        measurable.measure(
+                            Constraints.fixed(
+                                width = lockIconBounds.width,
+                                height = lockIconBounds.height,
+                            )
+                        )
+                    layout(
+                        width = placeable.width,
+                        height = placeable.height,
+                        alignmentLines =
+                            mapOf(
+                                BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+                                BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+                                BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+                                BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+                            ),
+                    ) {
+                        placeable.place(0, 0)
+                    }
+                },
+        )
     }
 
     /**
@@ -67,9 +126,9 @@
      * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
      * the same as the bounds of the sensor.
      */
-    fun lockIconBounds(
+    private fun lockIconBounds(
         context: Context,
-    ): Rect {
+    ): IntRect {
         val windowViewBounds = windowManager.currentWindowMetrics.bounds
         var widthPx = windowViewBounds.right.toFloat()
         if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
@@ -85,36 +144,33 @@
         val lockIconRadiusPx = (defaultDensity * 36).toInt()
 
         val udfpsLocation = authController.udfpsLocation
-        return if (authController.isUdfpsSupported && udfpsLocation != null) {
-            centerLockIcon(udfpsLocation, authController.udfpsRadius)
-        } else {
-            val scaleFactor = authController.scaleFactor
-            val bottomPaddingPx =
-                context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
-            val heightPx = windowViewBounds.bottom.toFloat()
+        val (center, radius) =
+            if (authController.isUdfpsSupported && udfpsLocation != null) {
+                Pair(
+                    IntOffset(
+                        x = udfpsLocation.x,
+                        y = udfpsLocation.y,
+                    ),
+                    authController.udfpsRadius.toInt(),
+                )
+            } else {
+                val scaleFactor = authController.scaleFactor
+                val bottomPaddingPx =
+                    context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+                val heightPx = windowViewBounds.bottom.toFloat()
 
-            centerLockIcon(
-                Point(
-                    (widthPx / 2).toInt(),
-                    (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
-                ),
-                lockIconRadiusPx * scaleFactor
-            )
-        }
-    }
+                Pair(
+                    IntOffset(
+                        x = (widthPx / 2).toInt(),
+                        y =
+                            (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
+                                .toInt(),
+                    ),
+                    (lockIconRadiusPx * scaleFactor).toInt(),
+                )
+            }
 
-    private fun centerLockIcon(
-        center: Point,
-        radius: Float,
-    ): Rect {
-        return Rect().apply {
-            set(
-                center.x - radius.toInt(),
-                center.y - radius.toInt(),
-                center.x + radius.toInt(),
-                center.y + radius.toInt(),
-            )
-        }
+        return IntRect(center, radius)
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f135be2..c547e2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,36 +16,25 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+    private val viewModel: NotificationsPlaceholderViewModel,
+) {
     @Composable
     fun SceneScope.Notifications(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = NotificationsElementKey,
+        NotificationStack(
+            viewModel = viewModel,
+            isScrimVisible = false,
             modifier = modifier,
-        ) {
-            Box(
-                modifier = Modifier.fillMaxSize().background(Color.Yellow),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Notifications",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
+        )
     }
 }
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 0000000..44b0535
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+    private val viewModel: KeyguardSettingsMenuViewModel,
+    private val longPressViewModel: KeyguardLongPressViewModel,
+    private val vibratorHelper: VibratorHelper,
+    private val activityStarter: ActivityStarter,
+) {
+    @Composable
+    @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+    fun SettingsMenu(
+        onPlaced: (Rect?) -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
+        val (disposableHandle, setDisposableHandle) =
+            remember { mutableStateOf<DisposableHandle?>(null) }
+        AndroidView(
+            factory = { context ->
+                LayoutInflater.from(context)
+                    .inflate(
+                        R.layout.keyguard_settings_popup_menu,
+                        null,
+                    )
+                    .apply {
+                        isVisible = false
+                        alpha = 0f
+
+                        setDisposableHandle(
+                            KeyguardSettingsViewBinder.bind(
+                                view = this,
+                                viewModel = viewModel,
+                                longPressViewModel = longPressViewModel,
+                                rootViewModel = null,
+                                vibratorHelper = vibratorHelper,
+                                activityStarter = activityStarter,
+                            )
+                        )
+                    }
+            },
+            onRelease = { disposableHandle?.dispose() },
+            modifier =
+                modifier
+                    .padding(
+                        bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+                    )
+                    .padding(
+                        horizontal =
+                            dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+                    )
+                    .onPlaced { coordinates ->
+                        onPlaced(
+                            if (!coordinates.size.toSize().isEmpty()) {
+                                Rect(coordinates.positionInParent(), coordinates.size.toSize())
+                            } else {
+                                null
+                            }
+                        )
+                    },
+        )
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b9d6643..2bfa7d9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
@@ -51,12 +53,13 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var messageBuffer: MessageBuffer? = null
-        set(value) {
-            logger = if (value != null) Logger(value, TAG) else null
-        }
-
-    private var logger: Logger? = null
+    // To protect us from issues from this being null while the TextView constructor is running, we
+    // implement the get method and ensure a value is returned before initialization is complete.
+    private var logger = DEFAULT_LOGGER
+        get() = field ?: DEFAULT_LOGGER
+    var messageBuffer: MessageBuffer
+        get() = logger.buffer
+        set(value) { logger = Logger(value, TAG) }
 
     private val time = Calendar.getInstance()
 
@@ -133,8 +136,8 @@
     }
 
     override fun onAttachedToWindow() {
+        logger.d("onAttachedToWindow")
         super.onAttachedToWindow()
-        logger?.d("onAttachedToWindow")
         refreshFormat()
     }
 
@@ -150,13 +153,13 @@
         time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
-        logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
+        logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
         // Setting text actually triggers a layout pass (because the text view is set to
         // wrap_content width and TextView always relayouts for this). Avoid needless
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-            logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+            logger.d({ "refreshTime: done setting new time text to: $str1" }) {
                 str1 = formattedText?.toString()
             }
             // Because the TextLayout may mutate under the hood as a result of the new text, we
@@ -165,21 +168,22 @@
             // without being notified TextInterpolator being notified.
             if (layout != null) {
                 textAnimator?.updateLayout(layout)
-                logger?.d("refreshTime: done updating textAnimator layout")
+                logger.d("refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            logger?.d("refreshTime: after requestLayout")
+            logger.d("refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
+        logger.d({ "onTimeZoneChanged($str1)" }) { str1 = timeZone?.toString() }
         time.timeZone = timeZone
         refreshFormat()
-        logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
     }
 
     @SuppressLint("DrawAllocation")
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        logger.d("onMeasure")
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val animator = textAnimator
         if (animator == null) {
@@ -189,10 +193,10 @@
         } else {
             animator.updateLayout(layout)
         }
-        logger?.d("onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
+        logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
         // Use textAnimator to render text if animation is enabled.
         // Otherwise default to using standard draw functions.
         if (isAnimationEnabled) {
@@ -201,22 +205,23 @@
         } else {
             super.onDraw(canvas)
         }
-        logger?.d("onDraw")
     }
 
     override fun invalidate() {
+        @Suppress("UNNECESSARY_SAFE_CALL")
+        // logger won't be initialized when called by TextView's constructor
+        logger.d("invalidate")
         super.invalidate()
-        logger?.d("invalidate")
     }
 
     override fun onTextChanged(
-            text: CharSequence,
-            start: Int,
-            lengthBefore: Int,
-            lengthAfter: Int
+        text: CharSequence,
+        start: Int,
+        lengthBefore: Int,
+        lengthAfter: Int
     ) {
+        logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -230,7 +235,7 @@
     }
 
     fun animateColorChange() {
-        logger?.d("animateColorChange")
+        logger.d("animateColorChange")
         setTextStyle(
             weight = lockScreenWeight,
             textSize = -1f,
@@ -252,7 +257,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        logger?.d("animateAppearOnLockscreen")
+        logger.d("animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -278,7 +283,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        logger?.d("animateFoldAppear")
+        logger.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -305,7 +310,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        logger?.d("animateCharge")
+        logger.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -329,7 +334,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logger?.d("animateDoze")
+        logger.d("animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -448,7 +453,7 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
-        logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
+        logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
         refreshTime()
@@ -552,6 +557,8 @@
 
     companion object {
         private val TAG = AnimatableClockView::class.simpleName!!
+        private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG)
+
         const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
         private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
         private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cdd074d..41bde52 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,20 +21,16 @@
 import android.net.Uri
 import android.os.UserHandle
 import android.provider.Settings
-import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogMessageImpl
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
@@ -77,32 +73,6 @@
     return result ?: value
 }
 
-private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
-
-private inline fun Logger?.tryLog(
-    tag: String,
-    level: LogLevel,
-    messageInitializer: MessageInitializer,
-    noinline messagePrinter: MessagePrinter,
-    ex: Throwable? = null,
-) {
-    if (this != null) {
-        // Wrap messagePrinter to convert it from crossinline to noinline
-        this.log(level, messagePrinter, ex, messageInitializer)
-    } else {
-        messageInitializer(TMP_MESSAGE)
-        val msg = messagePrinter(TMP_MESSAGE)
-        when (level) {
-            LogLevel.VERBOSE -> Log.v(tag, msg, ex)
-            LogLevel.DEBUG -> Log.d(tag, msg, ex)
-            LogLevel.INFO -> Log.i(tag, msg, ex)
-            LogLevel.WARNING -> Log.w(tag, msg, ex)
-            LogLevel.ERROR -> Log.e(tag, msg, ex)
-            LogLevel.WTF -> Log.wtf(tag, msg, ex)
-        }
-    }
-}
-
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
@@ -114,12 +84,15 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
-    messageBuffer: MessageBuffer? = null,
+    val clockBuffers: ClockMessageBuffers? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
     var isTransitClockEnabled: Boolean = false,
 ) {
     private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
+    private val logger: Logger =
+        Logger(clockBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG)
+
     interface ClockChangeListener {
         // Called when the active clock changes
         fun onCurrentClockChanged() {}
@@ -128,7 +101,6 @@
         fun onAvailableClocksChanged() {}
     }
 
-    private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -157,21 +129,15 @@
 
                 val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
                 if (knownClocks == null) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.WARNING,
-                        { str1 = manager.getPackage() },
-                        { "Loading unrecognized clock package: $str1" }
-                    )
+                    logger.w({ "Loading unrecognized clock package: $str1" }) {
+                        str1 = manager.getPackage()
+                    }
                     return true
                 }
 
-                logger.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = manager.getPackage() },
-                    { "Skipping initial load of known clock package package: $str1" }
-                )
+                logger.i({ "Skipping initial load of known clock package package: $str1" }) {
+                    str1 = manager.getPackage()
+                }
 
                 var isCurrentClock = false
                 var isClockListChanged = false
@@ -185,19 +151,14 @@
                         }
 
                     if (manager != info.manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on attach: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on attach: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info.manager.toString()
+                            str3 = manager.toString()
+                        }
                         continue
                     }
 
@@ -219,6 +180,8 @@
                 pluginContext: Context,
                 manager: PluginLifecycleManager<ClockProviderPlugin>
             ) {
+                plugin.initialize(clockBuffers)
+
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
@@ -233,19 +196,14 @@
                         }
 
                     if (manager != info.manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on load: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on load: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info.manager.toString()
+                            str3 = manager.toString()
+                        }
                         manager.unloadPlugin()
                         continue
                     }
@@ -268,19 +226,14 @@
                     val id = clock.clockId
                     val info = availableClocks[id]
                     if (info?.manager != manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info?.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on unload: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on unload: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info?.manager.toString()
+                            str3 = manager.toString()
+                        }
                         continue
                     }
                     info.provider = null
@@ -350,7 +303,7 @@
 
                 ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+                logger.e("Failed to parse clock settings", ex)
                 null
             }
         settings = result
@@ -379,7 +332,7 @@
                 )
             }
         } catch (ex: Exception) {
-            logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+            logger.e("Failed to set clock settings", ex)
         }
         settings = value
     }
@@ -451,7 +404,8 @@
         }
 
     init {
-        // Register default clock designs
+        // Initialize & register default clock designs
+        defaultClockProvider.initialize(clockBuffers)
         for (clock in defaultClockProvider.getClocks()) {
             availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
         }
@@ -514,12 +468,7 @@
     fun verifyLoadedProviders() {
         val shouldSchedule = isQueued.compareAndSet(false, true)
         if (!shouldSchedule) {
-            logger.tryLog(
-                TAG,
-                LogLevel.VERBOSE,
-                {},
-                { "verifyLoadedProviders: shouldSchedule=false" }
-            )
+            logger.v("verifyLoadedProviders: shouldSchedule=false")
             return
         }
 
@@ -528,12 +477,7 @@
             synchronized(availableClocks) {
                 isQueued.set(false)
                 if (keepAllLoaded) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.INFO,
-                        {},
-                        { "verifyLoadedProviders: keepAllLoaded=true" }
-                    )
+                    logger.i("verifyLoadedProviders: keepAllLoaded=true")
                     // Enforce that all plugins are loaded if requested
                     for ((_, info) in availableClocks) {
                         info.manager?.loadPlugin()
@@ -543,12 +487,7 @@
 
                 val currentClock = availableClocks[currentClockId]
                 if (currentClock == null) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.INFO,
-                        {},
-                        { "verifyLoadedProviders: currentClock=null" }
-                    )
+                    logger.i("verifyLoadedProviders: currentClock=null")
                     // Current Clock missing, load no plugins and use default
                     for ((_, info) in availableClocks) {
                         info.manager?.unloadPlugin()
@@ -556,12 +495,7 @@
                     return@launch
                 }
 
-                logger.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    {},
-                    { "verifyLoadedProviders: load currentClock" }
-                )
+                logger.i("verifyLoadedProviders: load currentClock")
                 val currentManager = currentClock.manager
                 currentManager?.loadPlugin()
 
@@ -577,30 +511,26 @@
 
     private fun onConnected(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
     }
 
     private fun onLoaded(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
 
         if (isCurrent) {
             triggerOnCurrentClockChanged()
@@ -609,16 +539,14 @@
 
     private fun onUnloaded(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
 
         if (isCurrent) {
             triggerOnCurrentClockChanged()
@@ -627,16 +555,14 @@
 
     private fun onDisconnected(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
     }
 
     fun getClocks(): List<ClockMetadata> {
@@ -676,23 +602,13 @@
         if (isEnabled && clockId.isNotEmpty()) {
             val clock = createClock(clockId)
             if (clock != null) {
-                logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
+                logger.i({ "Rendering clock $str1" }) { str1 = clockId }
                 return clock
             } else if (availableClocks.containsKey(clockId)) {
-                logger.tryLog(
-                    TAG,
-                    LogLevel.WARNING,
-                    { str1 = clockId },
-                    { "Clock $str1 not loaded; using default" }
-                )
+                logger.w({ "Clock $str1 not loaded; using default" }) { str1 = clockId }
                 verifyLoadedProviders()
             } else {
-                logger.tryLog(
-                    TAG,
-                    LogLevel.ERROR,
-                    { str1 = clockId },
-                    { "Clock $str1 not found; using default" }
-                )
+                logger.e({ "Clock $str1 not found; using default" }) { str1 = clockId }
             }
         }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 01c03b1..99d3216 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
 import com.android.systemui.plugins.clocks.WeatherData
@@ -41,8 +42,6 @@
 import java.util.Locale
 import java.util.TimeZone
 
-private val TAG = DefaultClockController::class.simpleName
-
 /**
  * Controls the default clock visuals.
  *
@@ -56,6 +55,7 @@
     private val settings: ClockSettings?,
     private val hasStepClockAnimation: Boolean = false,
     private val migratedClocks: Boolean = false,
+    messageBuffers: ClockMessageBuffers? = null,
 ) : ClockController {
     override val smallClock: DefaultClockFaceController
     override val largeClock: LargeClockFaceController
@@ -83,13 +83,15 @@
             DefaultClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_small, parent, false)
                     as AnimatableClockView,
-                settings?.seedColor
+                settings?.seedColor,
+                messageBuffers?.smallClockMessageBuffer
             )
         largeClock =
             LargeClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_large, parent, false)
                     as AnimatableClockView,
-                settings?.seedColor
+                settings?.seedColor,
+                messageBuffers?.largeClockMessageBuffer
             )
         clocks = listOf(smallClock.view, largeClock.view)
 
@@ -110,6 +112,7 @@
     open inner class DefaultClockFaceController(
         override val view: AnimatableClockView,
         var seedColor: Int?,
+        messageBuffer: MessageBuffer?,
     ) : ClockFaceController {
 
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -120,12 +123,6 @@
         override val config = ClockFaceConfig()
         override val layout = DefaultClockFaceLayout(view)
 
-        override var messageBuffer: MessageBuffer?
-            get() = view.messageBuffer
-            set(value) {
-                view.messageBuffer = value
-            }
-
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
             internal set
 
@@ -134,6 +131,7 @@
                 currentColor = seedColor!!
             }
             view.setColors(DOZE_COLOR, currentColor)
+            messageBuffer?.let { view.messageBuffer = it }
         }
 
         override val events =
@@ -188,7 +186,8 @@
     inner class LargeClockFaceController(
         view: AnimatableClockView,
         seedColor: Int?,
-    ) : DefaultClockFaceController(view, seedColor) {
+        messageBuffer: MessageBuffer?,
+    ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
         override val layout = DefaultClockFaceLayout(view)
         override val config =
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index a219be5..20f87a0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockSettings
@@ -35,6 +36,12 @@
     val hasStepClockAnimation: Boolean = false,
     val migratedClocks: Boolean = false
 ) : ClockProvider {
+    private var messageBuffers: ClockMessageBuffers? = null
+
+    override fun initialize(buffers: ClockMessageBuffers?) {
+        messageBuffers = buffers
+    }
+
     override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
 
     override fun createClock(settings: ClockSettings): ClockController {
@@ -49,6 +56,7 @@
             settings,
             hasStepClockAnimation,
             migratedClocks,
+            messageBuffers,
         )
     }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f0..4b21105 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@
 
     suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
         withContext(backgroundDispatcher) {
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = if (enabled) 1 else 0,
             )
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8..754d5dc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@
     ): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun set(
+    suspend fun setInt(
         name: String,
         value: Int,
     )
 
-    suspend fun get(
+    suspend fun getInt(
         name: String,
         defaultValue: Int = 0,
     ): Int
+
+    suspend fun getString(name: String): String?
 }
 
 class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@
             .flowOn(backgroundDispatcher)
     }
 
-    override suspend fun set(name: String, value: Int) {
+    override suspend fun setInt(name: String, value: Int) {
         withContext(backgroundDispatcher) {
             Settings.Secure.putInt(
                 contentResolver,
@@ -90,7 +92,7 @@
         }
     }
 
-    override suspend fun get(name: String, defaultValue: Int): Int {
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
         return withContext(backgroundDispatcher) {
             Settings.Secure.getInt(
                 contentResolver,
@@ -99,4 +101,13 @@
             )
         }
     }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getString(
+                contentResolver,
+                name,
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a07..37b9792 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@
         return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
     }
 
-    override suspend fun set(name: String, value: Int) {
+    override suspend fun setInt(name: String, value: Int) {
         settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
     }
 
-    override suspend fun get(name: String, defaultValue: Int): Int {
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
         return settings.value[name]?.toInt() ?: defaultValue
     }
+
+    override suspend fun getString(name: String): String? {
+        return settings.value[name]
+    }
 }
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
new file mode 100644
index 0000000..006b521
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import android.util.Log
+import com.android.systemui.log.LogMessageImpl
+
+/**
+ * A simple implementation of [MessageBuffer] that forwards messages to [android.util.Log]
+ * immediately. This defeats the intention behind [LogBuffer] and should only be used when
+ * [LogBuffer]s are unavailable in a certain context.
+ */
+class LogcatOnlyMessageBuffer(
+    val targetLogLevel: LogLevel,
+) : MessageBuffer {
+    private val singleMessage = LogMessageImpl.Factory.create()
+    private var isObtained: Boolean = false
+
+    @Synchronized
+    override fun obtain(
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable?,
+    ): LogMessage {
+        if (isObtained) {
+            throw UnsupportedOperationException(
+                "Message has already been obtained. Call order is incorrect."
+            )
+        }
+
+        singleMessage.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
+        isObtained = true
+        return singleMessage
+    }
+
+    @Synchronized
+    override fun commit(message: LogMessage) {
+        if (singleMessage != message) {
+            throw IllegalArgumentException("Message argument is not the expected message.")
+        }
+        if (!isObtained) {
+            throw UnsupportedOperationException(
+                "Message has not been obtained. Call order is incorrect."
+            )
+        }
+
+        if (message.level >= targetLogLevel) {
+            val strMessage = message.messagePrinter(message)
+            when (message.level) {
+                LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+                LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+                LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+                LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+                LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+                LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
+            }
+        }
+
+        isObtained = false
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index da97a12..1c1335f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -923,14 +923,14 @@
         doReturn(500).when(mResources)
                 .getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
                         .physical_fingerprint_sensor_center_screen_location_y));
-        mAuthController.onConfigurationChanged(null /* newConfig */);
+        mAuthController.onConfigChanged(null /* newConfig */);
 
         final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
 
         doReturn(1000).when(mResources)
                 .getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
                         .physical_fingerprint_sensor_center_screen_location_y));
-        mAuthController.onConfigurationChanged(null /* newConfig */);
+        mAuthController.onConfigChanged(null /* newConfig */);
 
         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a726b7c..b0beab9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -54,7 +55,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
@@ -101,7 +101,6 @@
     @Mock
     private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
     @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
-    @Mock private lateinit var secureSettings: SecureSettings
     @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var udfpsView: UdfpsView
@@ -117,6 +116,7 @@
     @Mock
     private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -174,6 +174,7 @@
                 mSelectedUserInteractor,
                 { deviceEntryUdfpsTouchOverlayViewModel },
                 { defaultUdfpsTouchOverlayViewModel },
+                shadeInteractor
             )
         block()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a4b55e7..e5da1f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -94,6 +94,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -205,6 +206,8 @@
     @Mock
     private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock
+    private ShadeInteractor mShadeInteractor;
+    @Mock
     private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
     @Mock
     private SessionTracker mSessionTracker;
@@ -328,6 +331,7 @@
                 mActivityLaunchAnimator,
                 mBiometricExecutor,
                 mPrimaryBouncerInteractor,
+                mShadeInteractor,
                 mSinglePointerTouchProcessor,
                 mSessionTracker,
                 mAlternateBouncerInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index ac16c13..13b53a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -70,6 +71,7 @@
     protected @Mock UdfpsController mUdfpsController;
     protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    protected @Mock ShadeInteractor mShadeInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     protected @Mock SelectedUserInteractor mSelectedUserInteractor;
@@ -149,7 +151,8 @@
                 mAlternateBouncerInteractor,
                 mUdfpsKeyguardAccessibilityDelegate,
                 mSelectedUserInteractor,
-                mKeyguardTransitionInteractor);
+                mKeyguardTransitionInteractor,
+                mShadeInteractor);
         return controller;
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 182712a..ddaa488 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -208,11 +208,11 @@
             val repository = initCommunalWidgetRepository()
             runCurrent()
 
-            val ids = listOf(104, 103, 101)
-            repository.updateWidgetOrder(ids)
+            val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+            repository.updateWidgetOrder(widgetIdToPriorityMap)
             runCurrent()
 
-            verify(communalWidgetDao).updateWidgetOrder(ids)
+            verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
         }
 
     @Test
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 1c5f221..4436be7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -41,16 +41,13 @@
 
 /** Interface for building clocks and providing information about those clocks */
 interface ClockProvider {
+    /** Initializes the clock provider with debug log buffers */
+    fun initialize(buffers: ClockMessageBuffers?)
+
     /** Returns metadata for all clocks this provider knows about */
     fun getClocks(): List<ClockMetadata>
 
     /** Initializes and returns the target clock design */
-    @Deprecated("Use overload with ClockSettings")
-    fun createClock(id: ClockId): ClockController {
-        return createClock(ClockSettings(id, null))
-    }
-
-    /** Initializes and returns the target clock design */
     fun createClock(settings: ClockSettings): ClockController
 
     /** A static thumbnail for rendering in some examples */
@@ -98,11 +95,20 @@
 
     /** Triggers for various animations */
     val animations: ClockAnimations
-
-    /** Some clocks may log debug information */
-    var messageBuffer: MessageBuffer?
 }
 
+/** For clocks that want to report debug information */
+data class ClockMessageBuffers(
+    /** Message buffer for general infra */
+    val infraMessageBuffer: MessageBuffer,
+
+    /** Message buffer for small clock renering */
+    val smallClockMessageBuffer: MessageBuffer,
+
+    /** Message buffer for large clock rendering */
+    val largeClockMessageBuffer: MessageBuffer,
+)
+
 /** Specifies layout information for the */
 interface ClockFaceLayout {
     /** All clock views to add to the root constraint layout before applying constraints. */
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 557fbf2..e6122a0 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -43,7 +43,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
         <FrameLayout
-            android:id="@+id/status_view_media_container"
+            android:id="@id/status_view_media_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:padding="@dimen/qs_media_padding"
diff --git a/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
new file mode 100644
index 0000000..2dce37d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners
+        android:topLeftRadius="28dp"
+        android:topRightRadius="28dp"/>
+    <solid android:color="?android:attr/colorBackground" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 8d7f7eb..a71782b 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -22,7 +22,7 @@
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/dialog_side_padding"
     android:paddingTop="@dimen/dialog_top_padding"
-    android:background="@*android:drawable/bottomsheet_background"
+    android:background="@drawable/connected_display_dialog_bg"
     android:paddingBottom="@dimen/dialog_bottom_padding">
 
     <ImageView
@@ -40,7 +40,7 @@
         android:id="@+id/connected_display_dialog_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+        android:layout_marginTop="16dp"
         android:gravity="center"
         android:text="@string/connected_display_dialog_start_mirroring"
         android:textAppearance="@style/TextAppearance.Dialog.Title" />
@@ -51,13 +51,14 @@
         android:layout_height="wrap_content"
         android:gravity="center"
         android:visibility="gone"
+        android:layout_marginTop="16dp"
         android:text="@string/connected_display_dialog_dual_display_stop_warning"
         android:textAppearance="@style/TextAppearance.Dialog.Body" />
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+        android:layout_marginTop="16dp"
         android:orientation="horizontal">
 
         <Button
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d511cab..80725c2 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -225,6 +225,8 @@
     <item type="id" name="communal_tutorial_indicator" />
     <item type="id" name="nssl_placeholder_barrier_bottom" />
     <item type="id" name="ambient_indication_container" />
+    <item type="id" name="status_view_media_container" />
+    <item type="id" name="smart_space_barrier_bottom" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b25a7..e7eb984 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3265,7 +3265,7 @@
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
     <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
-    <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
+    <string name="connected_display_dialog_dual_display_stop_warning">Your inner display will be mirrored. Your front display will be turned off.</string>
     <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
     <string name="mirror_display">Mirror display</string>
     <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 5b59e7d..2b41178 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,6 +34,9 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
@@ -70,6 +73,9 @@
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
     kotlincflags: ["-Xjvm-default=all"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -81,6 +87,9 @@
     static_kotlin_stdlib: false,
     java_version: "1.8",
     min_sdk_version: "current",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -100,4 +109,7 @@
     },
     java_version: "1.8",
     min_sdk_version: "current",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 92f66902..387f2e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -101,7 +101,7 @@
     }
 
     /** Alerts listener and plugin that the plugin has been created. */
-    public void onCreate() {
+    public synchronized void onCreate() {
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
@@ -128,7 +128,7 @@
     }
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
-    public void onDestroy() {
+    public synchronized void onDestroy() {
         logDebug("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
@@ -143,12 +143,13 @@
     /**
      * Loads and creates the plugin if it does not exist.
      */
-    public void loadPlugin() {
+    public synchronized void loadPlugin() {
         if (mPlugin != null) {
             logDebug("Load request when already loaded");
             return;
         }
 
+        // Both of these calls take about 1 - 1.5 seconds in test runs
         mPlugin = mPluginFactory.createPlugin();
         mPluginContext = mPluginFactory.createPluginContext();
         if (mPlugin == null || mPluginContext == null) {
@@ -171,7 +172,7 @@
      *
      * This will free the associated memory if there are not other references.
      */
-    public void unloadPlugin() {
+    public synchronized void unloadPlugin() {
         if (mPlugin == null) {
             logDebug("Unload request when already unloaded");
             return;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d8c1e41..131eb6b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -367,7 +367,7 @@
     /**
      * Corner radius that should be used on windows in order to cover the display.
      * These values are expressed in pixels because they should not respect display or font
-     * scaling, this means that we don't have to reload them on config changes.
+     * scaling. The corner radius may change when folding/unfolding the device.
      */
     public static float getWindowCornerRadius(Context context) {
         return ScreenDecorationsUtils.getWindowCornerRadius(context);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 76abad8..bcc2044 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -45,12 +45,11 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.LogLevel.DEBUG
-import com.android.systemui.log.dagger.KeyguardLargeClockLog
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockTickRate
 import com.android.systemui.plugins.clocks.AlarmData
 import com.android.systemui.plugins.clocks.WeatherData
@@ -91,117 +90,120 @@
     private val context: Context,
     @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
-    @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
-    @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
+    private val clockBuffers: ClockMessageBuffers,
     private val featureFlags: FeatureFlags,
     private val zenModeController: ZenModeController,
 ) {
+    var loggers = listOf(
+        clockBuffers.infraMessageBuffer,
+        clockBuffers.smallClockMessageBuffer,
+        clockBuffers.largeClockMessageBuffer
+    ).map { Logger(it, TAG) }
+
     var clock: ClockController? = null
+        get() = field
         set(value) {
-            smallClockOnAttachStateChangeListener?.let {
-                field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
+            disconnectClock(field)
+            field = value
+            connectClock(value)
+        }
+
+    private fun disconnectClock(clock: ClockController?) {
+        if (clock == null) { return; }
+        smallClockOnAttachStateChangeListener?.let {
+            clock.smallClock.view.removeOnAttachStateChangeListener(it)
+            smallClockFrame?.viewTreeObserver
+                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+        }
+        largeClockOnAttachStateChangeListener?.let {
+            clock.largeClock.view.removeOnAttachStateChangeListener(it)
+        }
+    }
+
+    private fun connectClock(clock: ClockController?) {
+        if (clock == null) { return; }
+        val clockStr = clock.toString()
+        loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
+
+        clock.initialize(resources, dozeAmount, 0f)
+
+        if (!regionSamplingEnabled) {
+            updateColors()
+        } else {
+            smallRegionSampler = createRegionSampler(
+                clock.smallClock.view,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                isLockscreen = true,
+                ::updateColors
+            ).apply { startRegionSampler() }
+
+            largeRegionSampler = createRegionSampler(
+                clock.largeClock.view,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                isLockscreen = true,
+                ::updateColors
+            ).apply { startRegionSampler() }
+
+            updateColors()
+        }
+        updateFontSizes()
+        updateTimeListeners()
+
+        weatherData?.let {
+            if (WeatherData.DEBUG) {
+                Log.i(TAG, "Pushing cached weather data to new clock: $it")
+            }
+            clock.events.onWeatherDataChanged(it)
+        }
+        zenData?.let {
+            clock.events.onZenDataChanged(it)
+        }
+        alarmData?.let {
+            clock.events.onAlarmDataChanged(it)
+        }
+
+        smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+            var pastVisibility: Int? = null
+            override fun onViewAttachedToWindow(view: View) {
+                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                // Match the asing for view.parent's layout classes.
+                smallClockFrame = (view.parent as ViewGroup)?.also { frame ->
+                    pastVisibility = frame.visibility
+                    onGlobalLayoutListener = OnGlobalLayoutListener {
+                        val currentVisibility = frame.visibility
+                        if (pastVisibility != currentVisibility) {
+                            pastVisibility = currentVisibility
+                            // when small clock is visible,
+                            // recalculate bounds and sample
+                            if (currentVisibility == View.VISIBLE) {
+                                smallRegionSampler?.stopRegionSampler()
+                                smallRegionSampler?.startRegionSampler()
+                            }
+                        }
+                    }
+                    frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
+                }
+            }
+
+            override fun onViewDetachedFromWindow(p0: View) {
                 smallClockFrame?.viewTreeObserver
                         ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
             }
-            largeClockOnAttachStateChangeListener?.let {
-                field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
-            }
-
-            field = value
-            if (value != null) {
-                smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.smallClock.messageBuffer = smallLogBuffer
-                largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.largeClock.messageBuffer = largeLogBuffer
-
-                value.initialize(resources, dozeAmount, 0f)
-
-                if (!regionSamplingEnabled) {
-                    updateColors()
-                } else {
-                    clock?.let {
-                        smallRegionSampler = createRegionSampler(
-                                it.smallClock.view,
-                                mainExecutor,
-                                bgExecutor,
-                                regionSamplingEnabled,
-                                isLockscreen = true,
-                                ::updateColors
-                        )?.apply { startRegionSampler() }
-
-                        largeRegionSampler = createRegionSampler(
-                                it.largeClock.view,
-                                mainExecutor,
-                                bgExecutor,
-                                regionSamplingEnabled,
-                                isLockscreen = true,
-                                ::updateColors
-                        )?.apply { startRegionSampler() }
-
-                        updateColors()
-                    }
-                }
-                updateFontSizes()
-                updateTimeListeners()
-                weatherData?.let {
-                    if (WeatherData.DEBUG) {
-                        Log.i(TAG, "Pushing cached weather data to new clock: $it")
-                    }
-                    value.events.onWeatherDataChanged(it)
-                }
-                zenData?.let {
-                    value.events.onZenDataChanged(it)
-                }
-                alarmData?.let {
-                    value.events.onAlarmDataChanged(it)
-                }
-
-                smallClockOnAttachStateChangeListener =
-                    object : OnAttachStateChangeListener {
-                        var pastVisibility: Int? = null
-                        override fun onViewAttachedToWindow(view: View) {
-                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                            // Match the asing for view.parent's layout classes.
-                            smallClockFrame = view.parent as ViewGroup
-                            smallClockFrame?.let { frame ->
-                                pastVisibility = frame.visibility
-                                onGlobalLayoutListener = OnGlobalLayoutListener {
-                                    val currentVisibility = frame.visibility
-                                    if (pastVisibility != currentVisibility) {
-                                        pastVisibility = currentVisibility
-                                        // when small clock is visible,
-                                        // recalculate bounds and sample
-                                        if (currentVisibility == View.VISIBLE) {
-                                            smallRegionSampler?.stopRegionSampler()
-                                            smallRegionSampler?.startRegionSampler()
-                                        }
-                                    }
-                                }
-                                frame.viewTreeObserver
-                                        .addOnGlobalLayoutListener(onGlobalLayoutListener)
-                            }
-                        }
-
-                        override fun onViewDetachedFromWindow(p0: View) {
-                            smallClockFrame?.viewTreeObserver
-                                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
-                        }
-                }
-                value.smallClock.view
-                        .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
-
-                largeClockOnAttachStateChangeListener =
-                    object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View) {
-                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                        }
-                        override fun onViewDetachedFromWindow(p0: View) {
-                        }
-                }
-                value.largeClock.view
-                        .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
-            }
         }
+        clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+        largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(p0: View) {
+                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+            }
+            override fun onViewDetachedFromWindow(p0: View) {}
+        }
+        clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+    }
 
     @VisibleForTesting
     var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
@@ -247,6 +249,7 @@
             largeClock.events.onRegionDarknessChanged(isRegionDark)
         }
     }
+
     protected open fun createRegionSampler(
         sampledView: View,
         mainExecutor: Executor?,
@@ -254,7 +257,7 @@
         regionSamplingEnabled: Boolean,
         isLockscreen: Boolean,
         updateColors: () -> Unit
-    ): RegionSampler? {
+    ): RegionSampler {
         return RegionSampler(
             sampledView,
             mainExecutor,
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 661ce2c..878a5d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -28,9 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
@@ -56,7 +55,7 @@
             FeatureFlags featureFlags,
             @Main Resources resources,
             LayoutInflater layoutInflater,
-            @KeyguardClockLog LogBuffer logBuffer) {
+            ClockMessageBuffers clockBuffers) {
         ClockRegistry registry = new ClockRegistry(
                 context,
                 pluginManager,
@@ -72,7 +71,7 @@
                         featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
                         migrateClocksToBlueprint()),
                 context.getString(R.string.lockscreen_clock_id_fallback),
-                logBuffer,
+                clockBuffers,
                 /* keepAllLoaded = */ false,
                 /* subTag = */ "System",
                 /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index c07a4d2..7295936 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui;
 
-import android.content.res.Configuration;
-
 import androidx.annotation.NonNull;
 
 import java.io.PrintWriter;
@@ -42,13 +40,6 @@
     /** Main entry point for implementations. Called shortly after SysUI startup. */
     void start();
 
-    /** Called when the device configuration changes. This will not be called before
-     * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
-     *
-     * @see android.app.Application#onConfigurationChanged(Configuration)  */
-    default void onConfigurationChanged(Configuration newConfig) {
-    }
-
     @Override
     default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 008de43..e03c627 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
@@ -109,7 +110,8 @@
  * for antialiasing and emulation purposes.
  */
 @SysUISingleton
-public class ScreenDecorations implements CoreStartable, Dumpable {
+public class ScreenDecorations implements
+        CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
     private static final boolean DEBUG_LOGGING = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -575,7 +577,7 @@
 
                     if (mPendingManualConfigUpdate) {
                         mPendingManualConfigUpdate = false;
-                        onConfigurationChanged(mContext.getResources().getConfiguration());
+                        onConfigChanged(mContext.getResources().getConfiguration());
                     }
                 }
             }
@@ -1062,7 +1064,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
             Log.i(TAG, "ScreenDecorations is disabled");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
new file mode 100644
index 0000000..044312b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ScreenDecorationsModule {
+    /** Start ScreenDecorations. */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenDecorations::class)
+    fun bindScreenDecorationsCoreStartable(impl: ScreenDecorations): CoreStartable
+
+    /** Listen to config changes for ScreenDecorations. */
+    @Binds
+    @IntoSet
+    fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c3f6480..01f6971 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,11 +37,11 @@
 import android.view.ThreadedRenderer;
 import android.view.View;
 
-import com.android.systemui.res.R;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.NotificationChannels;
 
@@ -354,19 +354,6 @@
             }
             configController.onConfigurationChanged(newConfig);
             Trace.endSection();
-            int len = mServices.length;
-            for (int i = 0; i < len; i++) {
-                if (mServices[i] != null) {
-                    if (Trace.isEnabled()) {
-                        Trace.traceBegin(
-                                Trace.TRACE_TAG_APP,
-                                mServices[i].getClass().getSimpleName()
-                                        + ".onConfigurationChanged()");
-                    }
-                    mServices[i].onConfigurationChanged(newConfig);
-                    Trace.endSection();
-                }
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 3cb6314..3ca95e1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -77,7 +77,7 @@
     @VisibleForTesting
     SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
 
-    private static class ControllerSupplier extends
+    private static class WindowMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
         private final Context mContext;
@@ -86,7 +86,7 @@
         private final SysUiState mSysUiState;
         private final SecureSettings mSecureSettings;
 
-        ControllerSupplier(Context context, Handler handler,
+        WindowMagnificationControllerSupplier(Context context, Handler handler,
                 WindowMagnifierCallback windowMagnifierCallback,
                 DisplayManager displayManager, SysUiState sysUiState,
                 SecureSettings secureSettings) {
@@ -118,7 +118,7 @@
     }
 
     @VisibleForTesting
-    DisplayIdIndexSupplier<WindowMagnificationController> mMagnificationControllerSupplier;
+    DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
 
     private static class SettingsSupplier extends
             DisplayIdIndexSupplier<MagnificationSettingsController> {
@@ -168,7 +168,7 @@
         mOverviewProxyService = overviewProxyService;
         mDisplayTracker = displayTracker;
         mA11yLogger = a11yLogger;
-        mMagnificationControllerSupplier = new ControllerSupplier(context,
+        mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
                 mHandler, mWindowMagnifierCallback,
                 displayManager, sysUiState, secureSettings);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
@@ -196,7 +196,8 @@
     private void updateSysUiStateFlag() {
         //TODO(b/187510533): support multi-display once SysuiState supports it.
         final WindowMagnificationController controller =
-                mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId());
+                mWindowMagnificationControllerSupplier.valueAt(
+                        mDisplayTracker.getDefaultDisplayId());
         if (controller != null) {
             controller.updateSysUIStateFlag();
         } else {
@@ -212,7 +213,7 @@
             float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
             @Nullable IRemoteMagnificationAnimationCallback callback) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
                     magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
@@ -222,7 +223,7 @@
     @MainThread
     void setScaleForWindowMagnification(int displayId, float scale) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.setScale(scale);
         }
@@ -231,7 +232,7 @@
     @MainThread
     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
         final WindowMagnificationController windowMagnificationcontroller =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationcontroller != null) {
             windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
         }
@@ -241,7 +242,7 @@
     void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
             IRemoteMagnificationAnimationCallback callback) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
                     callback);
@@ -252,7 +253,7 @@
     void disableWindowMagnification(int displayId,
             @Nullable IRemoteMagnificationAnimationCallback callback) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.deleteWindowMagnification(callback);
         }
@@ -417,7 +418,7 @@
     @MainThread
     private void onSetMagnifierSizeInternal(int displayId, int index) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.changeMagnificationSize(index);
         }
@@ -426,7 +427,7 @@
     @MainThread
     private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             windowMagnificationController.setDiagonalScrolling(enable);
         }
@@ -435,7 +436,7 @@
     @MainThread
     private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
             windowMagnificationController.setEditMagnifierSizeMode(enable);
         }
@@ -444,7 +445,7 @@
     @MainThread
     private void onModeSwitchInternal(int displayId, int newMode) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
         final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
         final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
@@ -463,7 +464,7 @@
     @MainThread
     private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
         final WindowMagnificationController windowMagnificationController =
-                mMagnificationControllerSupplier.get(displayId);
+                mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
             boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
             if (isWindowMagnifierActivated) {
@@ -495,7 +496,7 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println(TAG);
-        mMagnificationControllerSupplier.forEach(
+        mWindowMagnificationControllerSupplier.forEach(
                 magnificationController -> magnificationController.dump(pw));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7a8161e..da49201 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 
 import android.accessibilityservice.AccessibilityService;
@@ -57,6 +58,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
 
@@ -71,7 +73,7 @@
  * Class to register system actions with accessibility framework.
  */
 @SysUISingleton
-public class SystemActions implements CoreStartable {
+public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
     private static final String TAG = "SystemActions";
 
     /**
@@ -234,7 +236,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
         if (!locale.equals(mLocale)) {
             mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
new file mode 100644
index 0000000..4d6d784
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface SystemActionsModule {
+    /** Start SystemActions. */
+    @Binds
+    @IntoMap
+    @ClassKey(SystemActions::class)
+    fun bindSystemActionsStartable(sysui: SystemActions): CoreStartable
+
+    /** Listen to config changes for SystemActions. */
+    @Binds @IntoSet fun bindSystemActionsConfigChanges(sysui: SystemActions): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0bd4859..dde9f48 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1303,7 +1303,7 @@
         } else if (id == R.id.close_button) {
             setEditMagnifierSizeMode(false);
         } else {
-            animateBounceEffect();
+            animateBounceEffectIfNeeded();
         }
     }
 
@@ -1465,7 +1465,12 @@
         mBounceEffectDuration = duration;
     }
 
-    private void animateBounceEffect() {
+    private void animateBounceEffectIfNeeded() {
+        if (mMirrorView == null) {
+            // run the animation only if the mirror view is not null
+            return;
+        }
+
         final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
                 PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
                 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127..49e0df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@
  * Controls the interaction between {@link MagnetizedObject} and
  * {@link MagnetizedObject.MagneticTarget}.
  */
-class DismissAnimationController {
+class DragToInteractAnimationController {
     private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
     private static final float COMPLETELY_OPAQUE = 1.0f;
     private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@
     private float mMinDismissSize;
     private float mSizePercent;
 
-    DismissAnimationController(DismissView dismissView, MenuView menuView) {
+    DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
         mDismissView = dismissView;
         mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
         mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeUpMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cec..a270558 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
-    private DismissAnimationController.DismissCallback mDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mDismissCallback;
     private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@
     }
 
     void setDismissCallback(
-            DismissAnimationController.DismissCallback dismissCallback) {
+            DragToInteractAnimationController.DismissCallback dismissCallback) {
         mDismissCallback = dismissCallback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f..52e7b91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@
     private final PointF mMenuTranslationDown = new PointF();
     private boolean mIsDragging = false;
     private float mTouchSlop;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private Optional<Runnable> mOnActionDownEnd = Optional.empty();
 
     MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
-            DismissAnimationController dismissAnimationController) {
+            DragToInteractAnimationController dragToInteractAnimationController) {
         mMenuAnimationController = menuAnimationController;
-        mDismissAnimationController = dismissAnimationController;
+        mDragToInteractAnimationController = dragToInteractAnimationController;
     }
 
     @Override
@@ -67,7 +67,7 @@
                 mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
 
                 mMenuAnimationController.cancelAnimations();
-                mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+                mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
 
                 mOnActionDownEnd.ifPresent(Runnable::run);
                 break;
@@ -78,9 +78,10 @@
                         mMenuAnimationController.onDraggingStart();
                     }
 
-                    mDismissAnimationController.showDismissView(/* show= */ true);
+                    mDragToInteractAnimationController.showDismissView(/* show= */ true);
 
-                    if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+                            motionEvent)) {
                         mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                         mMenuAnimationController.moveToPositionYIfNeeded(
                                 mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@
                     mIsDragging = false;
 
                     if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                         mMenuAnimationController.fadeOutIfEnabled();
 
                         return true;
                     }
 
-                    if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+                            motionEvent)) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                         mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                     }
 
                     // Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3..62d5feb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final SecureSettings mSecureSettings;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private final MenuViewModel mMenuViewModel;
     private final Observer<Boolean> mDockTooltipObserver =
             this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
-        mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, mMenuView);
+        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
             }
 
             @Override
             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                     float velocityX, float velocityY, boolean wasFlungOut) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
 
             @Override
             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                 hideMenuAndShowMessage();
                 mDismissView.hide();
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
         });
 
         mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
         mMenuView.setMoveToTuckedListener(this);
 
@@ -243,7 +244,7 @@
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         mDismissView.updateResources();
-        mDismissAnimationController.updateResources();
+        mDragToInteractAnimationController.updateResources();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..83d415f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,6 +403,13 @@
 
             final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_layout, null, false);
+            /**
+             * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
+             * that accounts for iconView size, to prevent prompt resizing being visible to the
+             * user.
+             * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+             */
+            view.setVisibility(View.INVISIBLE);
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
                     getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5fba761..8a1a2da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -84,6 +84,7 @@
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -114,8 +115,12 @@
  * {@link com.android.keyguard.KeyguardUpdateMonitor}
  */
 @SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
-        AuthDialogCallback, DozeReceiver {
+public class AuthController implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks,
+        AuthDialogCallback,
+        DozeReceiver {
 
     private static final String TAG = "AuthController";
     private static final boolean DEBUG = true;
@@ -1297,7 +1302,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         updateSensorLocations();
 
         // TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 66fb8ca..7d9ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,16 +23,14 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.systemui.Dumpable
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.ViewController
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import java.io.PrintWriter
 
@@ -49,7 +47,7 @@
 abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
     view: T,
     protected val statusBarStateController: StatusBarStateController,
-    protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    protected val shadeInteractor: ShadeInteractor,
     protected val dialogManager: SystemUIDialogManager,
     private val dumpManager: DumpManager
 ) : ViewController<T>(view), Dumpable {
@@ -94,20 +92,18 @@
             // can make the view not visible; and we still want to listen for events
             // that may make the view visible again.
             repeatOnLifecycle(Lifecycle.State.CREATED) {
-                listenForBouncerExpansion(this)
+                listenForShadeExpansion(this)
             }
         }
     }
 
     @VisibleForTesting
-    open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+    suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
         return scope.launch {
-            primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
-                if (statusBarStateController.state != SHADE) {
-                    notificationShadeVisible = expansion > 0f
-                    view.onExpansionChanged(expansion)
-                    updatePauseAuth()
-                }
+            shadeInteractor.anyExpansion.collect { expansion ->
+                notificationShadeVisible = expansion > 0f
+                view.onExpansionChanged(expansion)
+                updatePauseAuth()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 03749a9..e7b0d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.biometrics
 
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 
 /**
@@ -26,13 +26,13 @@
 class UdfpsBpViewController(
     view: UdfpsBpView,
     statusBarStateController: StatusBarStateController,
-    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsBpView>(
     view,
     statusBarStateController,
-    primaryBouncerInteractor,
+    shadeInteractor,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 240728a..2fd13b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -89,6 +89,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -162,6 +163,7 @@
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
     @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    @NonNull private final ShadeInteractor mShadeInteractor;
     @Nullable private final TouchProcessor mTouchProcessor;
     @NonNull private final SessionTracker mSessionTracker;
     @NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
@@ -290,7 +292,8 @@
                         mKeyguardTransitionInteractor,
                         mSelectedUserInteractor,
                         mDeviceEntryUdfpsTouchOverlayViewModel,
-                        mDefaultUdfpsTouchOverlayViewModel
+                        mDefaultUdfpsTouchOverlayViewModel,
+                        mShadeInteractor
                     )));
         }
 
@@ -656,6 +659,7 @@
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+            @NonNull ShadeInteractor shadeInteractor,
             @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
             @NonNull SessionTracker sessionTracker,
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@@ -705,6 +709,7 @@
 
         mBiometricExecutor = biometricsExecutor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
+        mShadeInteractor = shadeInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index aabee93..b94a177 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -57,6 +57,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -107,6 +108,7 @@
     private val selectedUserInteractor: SelectedUserInteractor,
     private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
     private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+    private val shadeInteractor: ShadeInteractor,
 ) {
     private var overlayViewLegacy: UdfpsView? = null
         private set
@@ -277,7 +279,7 @@
                         updateAccessibilityViewLocation(sensorBounds)
                     },
                     statusBarStateController,
-                    primaryBouncerInteractor,
+                    shadeInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -303,6 +305,7 @@
                     udfpsKeyguardAccessibilityDelegate,
                     selectedUserInteractor,
                     transitionInteractor,
+                    shadeInteractor,
                 )
             }
             REASON_AUTH_BP -> {
@@ -310,7 +313,7 @@
                 UdfpsBpViewController(
                     view.addUdfpsView(R.layout.udfps_bp_view),
                     statusBarStateController,
-                    primaryBouncerInteractor,
+                    shadeInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -320,7 +323,7 @@
                 UdfpsFpmEmptyViewController(
                     view.addUdfpsView(R.layout.udfps_fpm_empty_view),
                     statusBarStateController,
-                    primaryBouncerInteractor,
+                    shadeInteractor,
                     dialogManager,
                     dumpManager
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index 88002e7..ab3fbb1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.biometrics
 
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 
 /**
@@ -28,13 +28,13 @@
 class UdfpsFpmEmptyViewController(
     view: UdfpsFpmEmptyView,
     statusBarStateController: StatusBarStateController,
-    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
     view,
     statusBarStateController,
-    primaryBouncerInteractor,
+    shadeInteractor,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 64148f6..9f17024 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -68,16 +69,17 @@
     systemUIDialogManager: SystemUIDialogManager,
     private val udfpsController: UdfpsController,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
-    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val transitionInteractor: KeyguardTransitionInteractor,
+    shadeInteractor: ShadeInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
         statusBarStateController,
-        primaryBouncerInteractor,
+        shadeInteractor,
         systemUIDialogManager,
         dumpManager,
     ) {
@@ -319,7 +321,7 @@
     }
 
     @VisibleForTesting
-    override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+    suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
         return scope.launch {
             primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
                 inputBouncerExpansion = bouncerExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 8ae6f87..307b985 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -19,6 +19,7 @@
 import android.content.res.Resources
 import com.android.internal.R
 import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
@@ -38,18 +39,30 @@
 import com.android.systemui.biometrics.udfps.OverlapDetector
 import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.android.systemui.util.concurrency.ThreadFactory
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import java.util.concurrent.Executor
 import javax.inject.Qualifier
 
 /** Dagger module for all things biometric. */
 @Module
 interface BiometricsModule {
+    /** Starts AuthController.  */
+    @Binds
+    @IntoMap
+    @ClassKey(AuthController::class)
+    fun bindAuthControllerStartable(service: AuthController): CoreStartable
+
+    /** Listen to config changes for AuthController. */
+    @Binds
+    @IntoSet
+    fun bindAuthControllerConfigChanges(service: AuthController): ConfigurationListener
 
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 90e4a38..a7fb6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -97,7 +97,13 @@
 
         val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
         val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-
+        /**
+         * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
+         * accounts for iconView size, to prevent prompt resizing being visible to the user.
+         *
+         * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+         */
+        iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
         PromptIconViewBinder.bind(
             iconView,
             iconOverlayView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 7e16d1e..f340bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,7 +30,6 @@
 import androidx.core.view.doOnLayout
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@
 import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
 import com.android.systemui.biometrics.ui.viewmodel.isSmall
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -92,8 +93,22 @@
             // TODO(b/251476085): migrate the legacy panel controller and simplify this
             view.repeatWhenAttached {
                 var currentSize: PromptSize? = null
+
                 lifecycleScope.launch {
-                    viewModel.size.collect { size ->
+                    /**
+                     * View is only set visible in BiometricViewSizeBinder once PromptSize is
+                     * determined that accounts for iconView size, to prevent prompt resizing being
+                     * visible to the user.
+                     *
+                     * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+                     *   layout is implemented
+                     */
+                    combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+                        (isIconViewLoaded, size) ->
+                        if (!isIconViewLoaded) {
+                            return@collect
+                        }
+
                         // prepare for animated size transitions
                         for (v in viewsToHideWhenSmall) {
                             v.showTextOrHide(forceHide = size.isSmall)
@@ -196,8 +211,9 @@
                                     }
                                 }
                             }
-
                             currentSize = size
+                            view.visibility = View.VISIBLE
+                            viewModel.setIsIconViewLoaded(false)
                             notifyAccessibilityChanged()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446..c36e0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
-    private val biometricStatusInteractor: BiometricStatusInteractor,
-    private val displayStateInteractor: DisplayStateInteractor,
-    private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
-    private val fpsUnlockTracker: FpsUnlockTracker,
-    private val layoutInflater: LayoutInflater,
-    private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
-    private val sfpsSensorInteractor: SideFpsSensorInteractor,
-    private val windowManager: WindowManager
+    private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+    private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+    private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+    private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+    private val layoutInflater: Lazy<LayoutInflater>,
+    private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+    private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+    private val windowManager: Lazy<WindowManager>
 ) : CoreStartable {
 
     override fun start() {
         if (!SideFpsControllerRefactor.isEnabled) {
             return
         }
+
         applicationScope
             .launch {
-                combine(
-                        biometricStatusInteractor.sfpsAuthenticationReason,
-                        deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
-                        sideFpsProgressBarViewModel.isVisible,
-                        ::Triple
-                    )
-                    .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
-                    .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
-                        val (
-                            systemServerAuthReason,
-                            showIndicatorForDeviceEntry,
-                            progressBarIsVisible) =
-                            combinedFlows
-                        if (!isInRearDisplayMode) {
-                            if (progressBarIsVisible) {
-                                hide()
-                            } else if (systemServerAuthReason != NotRunning) {
-                                show()
-                            } else if (showIndicatorForDeviceEntry) {
-                                show()
-                            } else {
-                                hide()
+                sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+                    if (isSfpsAvailable) {
+                        combine(
+                                biometricStatusInteractor.get().sfpsAuthenticationReason,
+                                deviceEntrySideFpsOverlayInteractor
+                                    .get()
+                                    .showIndicatorForDeviceEntry,
+                                sideFpsProgressBarViewModel.get().isVisible,
+                                ::Triple
+                            )
+                            .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+                            .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+                                val (
+                                    systemServerAuthReason,
+                                    showIndicatorForDeviceEntry,
+                                    progressBarIsVisible) =
+                                    combinedFlows
+                                if (!isInRearDisplayMode) {
+                                    if (progressBarIsVisible) {
+                                        hide()
+                                    } else if (systemServerAuthReason != NotRunning) {
+                                        show()
+                                    } else if (showIndicatorForDeviceEntry) {
+                                        show()
+                                    } else {
+                                        hide()
+                                    }
+                                }
                             }
-                        }
                     }
+                }
             }
-            .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+            .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
     }
 
     private var overlayView: View? = null
@@ -113,29 +121,29 @@
             if (it.isAttachedToWindow) {
                 lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie?.pauseAnimation()
-                windowManager.removeView(it)
+                windowManager.get().removeView(it)
             }
         }
 
-        overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
         val overlayViewModel =
             SideFpsOverlayViewModel(
                 applicationContext,
-                biometricStatusInteractor,
-                deviceEntrySideFpsOverlayInteractor,
-                displayStateInteractor,
-                sfpsSensorInteractor,
-                sideFpsProgressBarViewModel
+                biometricStatusInteractor.get(),
+                deviceEntrySideFpsOverlayInteractor.get(),
+                displayStateInteractor.get(),
+                sfpsSensorInteractor.get(),
+                sideFpsProgressBarViewModel.get()
             )
-        bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+        bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
         overlayView!!.visibility = View.INVISIBLE
-        windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+        windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
     }
 
     /** Hide the side fingerprint sensor indicator */
     private fun hide() {
         if (overlayView != null) {
-            windowManager.removeView(overlayView)
+            windowManager.get().removeView(overlayView)
             overlayView = null
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 6d0a58e..d899827e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -192,6 +192,28 @@
     val iconViewModel: PromptIconViewModel =
         PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
 
+    private val _isIconViewLoaded = MutableStateFlow(false)
+
+    /**
+     * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
+     * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
+     * asset to be loaded before determining the prompt size.
+     */
+    val isIconViewLoaded: Flow<Boolean> =
+        combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
+            ->
+            if (credentialKind is PromptKind.Biometric) {
+                isIconViewLoaded
+            } else {
+                true
+            }
+        }
+
+    // Sets whether the prompt's iconView animation has been loaded in the view yet.
+    fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
+        _isIconViewLoaded.value = iconViewLoaded
+    }
+
     /** Padding for prompt UI elements */
     val promptPadding: Flow<Rect> =
         combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index a12db6f..779446d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -117,10 +117,10 @@
     fun updateItemRank(itemUid: Long, order: Int)
 
     @Transaction
-    fun updateWidgetOrder(ids: List<Int>) {
-        ids.forEachIndexed { index, it ->
-            val widget = getWidgetByIdNow(it)
-            updateItemRank(widget.itemId, ids.size - index)
+    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+        widgetIdToPriorityMap.forEach { (id, priority) ->
+            val widget = getWidgetByIdNow(id)
+            updateItemRank(widget.itemId, priority)
         }
     }
 
@@ -129,7 +129,7 @@
         return insertWidget(
             widgetId = widgetId,
             componentName = provider.flattenToString(),
-            insertItemRank(priority),
+            itemId = insertItemRank(priority),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ded5581..d1bbe57 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -62,8 +62,12 @@
     /** Delete a widget by id from app widget service and the database. */
     fun deleteWidget(widgetId: Int) {}
 
-    /** Update the order of widgets in the database. */
-    fun updateWidgetOrder(ids: List<Int>) {}
+    /**
+     * Update the order of widgets in the database.
+     *
+     * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+     */
+    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -168,11 +172,11 @@
         }
     }
 
-    override fun updateWidgetOrder(ids: List<Int>) {
+    override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
         applicationScope.launch(bgDispatcher) {
-            communalWidgetDao.updateWidgetOrder(ids)
+            communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
             logger.i({ "Updated the order of widget list with ids: $str1." }) {
-                str1 = ids.toString()
+                str1 = widgetIdToPriorityMap.toString()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index e342c6b..0f4e583 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -102,8 +102,13 @@
     /** Delete a widget by id. */
     fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
 
-    /** Reorder widgets. The order in the list will be their display order in the hub. */
-    fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
+    /**
+     * Reorder the widgets.
+     *
+     * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+     */
+    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
+        widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
 
     /** A list of widget content to be displayed in the communal hub. */
     val widgetContent: Flow<List<CommunalContentModel.Widget>> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index bb9b4b5..3ae5229 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import java.util.UUID
 
 /** Encapsulates data for a communal content. */
 sealed interface CommunalContentModel {
@@ -39,6 +40,13 @@
         override val size = CommunalContentSize.HALF
     }
 
+    /** A placeholder item representing a new widget being added */
+    class WidgetPlaceholder : CommunalContentModel {
+        override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+        // Same as widget size.
+        override val size = CommunalContentSize.HALF
+    }
+
     class Tutorial(
         id: Int,
         override val size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 708f137..577e404 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.content.ComponentName
 import android.os.PowerManager
 import android.os.SystemClock
 import android.view.MotionEvent
@@ -53,6 +54,13 @@
         communalInteractor.setTransitionState(transitionState)
     }
 
+    /**
+     * Called when a widget is added via drag and drop from the widget picker into the communal hub.
+     */
+    fun onAddWidget(componentName: ComponentName, priority: Int) {
+        communalInteractor.addWidget(componentName, priority)
+    }
+
     // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
     //  touches anymore.
     /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
@@ -82,8 +90,14 @@
     /** Called as the UI requests deleting a widget. */
     open fun onDeleteWidget(id: Int) {}
 
-    /** Called as the UI requests reordering widgets. */
-    open fun onReorderWidgets(ids: List<Int>) {}
+    /**
+     * Called as the UI requests reordering widgets.
+     *
+     * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
+     *   add a new item in the middle, provide the priorities of existing widgets as if the new item
+     *   existed, and then, call [onAddWidget] to add the new item at intended order.
+     */
+    open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
 
     /** Called as the UI requests opening the widget editor. */
     open fun onOpenWidgetEditor() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c82e000..368df9e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -47,5 +47,6 @@
 
     override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
-    override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+    override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
+        communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 0a13e48..c936c63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -40,6 +40,7 @@
     private var windowManagerService: IWindowManager? = null,
 ) : ComponentActivity() {
     companion object {
+        private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
         private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
         private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
         private const val TAG = "EditWidgetsActivity"
@@ -49,10 +50,23 @@
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
                 RESULT_OK -> {
-                    result.data
-                        ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
-                        ?.let { communalInteractor.addWidget(it, 0) }
-                        ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+                    result.data?.let { intent ->
+                        val isPendingWidgetDrag =
+                            intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+                        // Nothing to do when a widget is being dragged & dropped. The drop
+                        // target in the communal grid will receive the widget to be added (if
+                        // the user drops it over).
+                        if (!isPendingWidgetDrag) {
+                            intent
+                                .getParcelableExtra(
+                                    Intent.EXTRA_COMPONENT_NAME,
+                                    ComponentName::class.java
+                                )
+                                ?.let { communalInteractor.addWidget(it, 0) }
+                                ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+                        }
+                    }
+                        ?: run { Log.w(TAG, "No data in result.") }
                 }
                 else ->
                     Log.w(
@@ -65,8 +79,6 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        setShowWhenLocked(true)
-
         setCommunalEditWidgetActivityContent(
             activity = this,
             viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 236c5b8..50f861f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,6 +23,8 @@
 import android.hardware.SensorPrivacyManager;
 
 import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.battery.BatterySaverModule;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -34,6 +36,7 @@
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.qs.dagger.QSModule;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.reardisplay.RearDisplayModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
@@ -59,6 +62,7 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.toast.ToastModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
@@ -89,19 +93,23 @@
         CollapsedStatusBarFragmentStartableModule.class,
         GestureModule.class,
         HeadsUpModule.class,
+        KeyboardShortcutsModule.class,
         MediaModule.class,
         MultiUserUtilsModule.class,
         NavigationBarControllerModule.class,
         PowerModule.class,
         QSModule.class,
-        ShadeModule.class,
+        RearDisplayModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
-        SceneContainerFrameworkModule.class,
+        ScreenDecorationsModule.class,
+        SystemActionsModule.class,
+        ShadeModule.class,
         StartCentralSurfacesModule.class,
+        SceneContainerFrameworkModule.class,
+        ToastModule.class,
         VolumeModule.class,
-        WallpaperModule.class,
-        KeyboardShortcutsModule.class
+        WallpaperModule.class
 })
 public abstract class ReferenceSystemUIModule {
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d041acb..ac71664 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -19,12 +19,9 @@
 import com.android.keyguard.KeyguardBiometricLockoutLogger
 import com.android.systemui.CoreStartable
 import com.android.systemui.LatencyTester
-import com.android.systemui.ScreenDecorations
 import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.SystemActions
 import com.android.systemui.accessibility.Magnification
 import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
 import com.android.systemui.controls.dagger.StartControlsStartableModule
@@ -46,10 +43,6 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.power.PowerUI
-import com.android.systemui.reardisplay.RearDisplayDialogController
-import com.android.systemui.recents.Recents
-import com.android.systemui.recents.ScreenPinningRequest
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -61,11 +54,9 @@
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
 import com.android.systemui.usb.StorageNotification
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
-import com.android.systemui.volume.VolumeUI
 import com.android.systemui.wallpapers.dagger.WallpaperModule
 import com.android.systemui.wmshell.WMShell
 import dagger.Binds
@@ -74,7 +65,12 @@
 import dagger.multibindings.IntoMap
 
 /**
- * Collection of {@link CoreStartable}s that should be run on AOSP.
+ * DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
+ *
+ * Add a feature specific daggger module for what you are working on. Bind your CoreStartable there.
+ * Include that module where it is needed.
+ *
+ * @deprecated
  */
 @Module(
     includes = [
@@ -85,12 +81,6 @@
     ]
 )
 abstract class SystemUICoreStartableModule {
-    /** Inject into AuthController.  */
-    @Binds
-    @IntoMap
-    @ClassKey(AuthController::class)
-    abstract fun bindAuthController(service: AuthController): CoreStartable
-
     /** Inject into BiometricNotificationService */
     @Binds
     @IntoMap
@@ -158,18 +148,6 @@
     @PerUser
     abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
 
-    /** Inject into PowerUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(PowerUI::class)
-    abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
-    /** Inject into Recents.  */
-    @Binds
-    @IntoMap
-    @ClassKey(Recents::class)
-    abstract fun bindRecents(sysui: Recents): CoreStartable
-
     /** Inject into ImmersiveModeConfirmation.  */
     @Binds
     @IntoMap
@@ -182,12 +160,6 @@
     @ClassKey(RingtonePlayer::class)
     abstract fun bind(sysui: RingtonePlayer): CoreStartable
 
-    /** Inject into ScreenDecorations.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenDecorations::class)
-    abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
-
     /** Inject into GesturePointerEventHandler. */
     @Binds
     @IntoMap
@@ -218,23 +190,12 @@
     @ClassKey(StorageNotification::class)
     abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
 
-    /** Inject into SystemActions.  */
-    @Binds
-    @IntoMap
-    @ClassKey(SystemActions::class)
-    abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
-
     /** Inject into ThemeOverlayController.  */
     @Binds
     @IntoMap
     @ClassKey(ThemeOverlayController::class)
     abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
 
-    /** Inject into ToastUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ToastUI::class)
-    abstract fun bindToastUI(service: ToastUI): CoreStartable
 
     /** Inject into MediaOutputSwitcherDialogUI.  */
     @Binds
@@ -242,12 +203,6 @@
     @ClassKey(MediaOutputSwitcherDialogUI::class)
     abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
 
-    /** Inject into VolumeUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(VolumeUI::class)
-    abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
     /** Inject into Magnification.  */
     @Binds
     @IntoMap
@@ -293,11 +248,6 @@
     abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
 
 
-    /** Inject into RearDisplayDialogController) */
-    @Binds
-    @IntoMap
-    @ClassKey(RearDisplayDialogController::class)
-    abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
 
     /** Inject into StylusUsiPowerStartable) */
     @Binds
@@ -361,9 +311,4 @@
     @IntoMap
     @ClassKey(KeyguardDismissBinder::class)
     abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenPinningRequest::class)
-    abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f6db978..1b35005 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@
     @JvmField
     val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag("split_shade_subpixel_optimization")
 
-    // TODO(b/288868056): Tracking Bug
-    @JvmField
-    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher")
-
     // TODO(b/278761837): Tracking Bug
     @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter")
 
@@ -609,11 +605,6 @@
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
         unreleasedFlag("bigpicture_notification_lazy_loading")
 
-    // TODO(b/292062937): Tracking bug
-    @JvmField
-    val NOTIFICATION_CLEARABLE_REFACTOR =
-            unreleasedFlag("notification_clearable_refactor")
-
     // TODO(b/283740863): Tracking Bug
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
             SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
         }
     }
+
+    /** The arrow navigation that was operating the slider has stopped. */
+    fun onArrowUp() {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
 
     override suspend fun iterateState(event: SliderEvent) {
         when (currentState) {
-            SliderState.IDLE -> handleIdle(event.type)
+            SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
             SliderState.WAIT -> handleWait(event.type, event.currentProgress)
             SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
             SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
             SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
             SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
             SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+                handleArrowContinuous(event.type, event.currentProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
         }
         latestProgress = event.currentProgress
     }
 
-    private fun handleIdle(newEventType: SliderEventType) {
+    private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
         if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
             timerJob = launchTimer()
             // The WAIT state will wait for the timer to complete or a slider progress to occur.
             // This will disambiguate between an imprecise touch that acquires the slider handle,
             // and a select and jump operation in the slider track.
             setState(SliderState.WAIT)
+        } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+            val state =
+                if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                else SliderState.ARROW_HANDLE_MOVED_ONCE
+            setState(state)
         }
     }
 
@@ -176,6 +185,13 @@
             SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
             SliderState.JUMP_TRACK_LOCATION_SELECTED ->
                 sliderListener.onProgressJump(latestProgress)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+                executeOnBookend()
+                // This transitory execution must also reset the state
+                resetState()
+            }
             else -> {}
         }
     }
@@ -204,6 +220,43 @@
             currentProgress <= config.lowerBookendThreshold
     }
 
+    private fun handleArrowOnce(newEventType: SliderEventType) {
+        val nextState =
+            when (newEventType) {
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+                    SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+        val reachedBookend = bookendReached(currentProgress)
+        val nextState =
+            when (newEventType) {
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+                    if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                    else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                }
+                else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowBookend() = setState(SliderState.IDLE)
+
     @VisibleForTesting
     fun setState(state: SliderState) {
         currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
     /* The slider has stopped tracking touch events. */
     STOPPED_TRACKING_TOUCH,
     /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
-    EXTERNAL_STIMULUS_RELEASE,
+    ARROW_UP,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
     DRAG_HANDLE_REACHED_BOOKEND,
     /* A location in the slider track has been selected. */
     JUMP_TRACK_LOCATION_SELECTED,
-    /* The slider handled moved to a bookend after it was selected. */
+    /* The slider handle moved to a bookend after it was selected. */
     JUMP_BOOKEND_SELECTED,
+    /** The slider handle moved due to single select-and-arrow operation */
+    ARROW_HANDLE_MOVED_ONCE,
+    /** The slider handle moves continuously due to constant select-and-arrow operations */
+    ARROW_HANDLE_MOVES_CONTINUOUSLY,
+    /** The slider handle reached a bookend due to a select-and-arrow operation */
+    ARROW_HANDLE_REACHED_BOOKEND,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index c490ce7..342325f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -30,7 +30,7 @@
 import android.os.Binder
 import android.os.Bundle
 import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.runBlocking
+import com.android.app.tracing.coroutines.runBlocking
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
 import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 1f69cc0..0d40511 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -397,8 +397,10 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_DATE_CHANGED);
             filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-            getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
-                    null /* scheduler */);
+            mBgHandler.post(() -> {
+                getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+                        null /* scheduler */);
+            });
             mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
             mRegistered = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 4d60dd0..17d7836 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -626,17 +626,19 @@
             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
             return
         }
-        detectCancellationSignal?.cancel()
-        detectCancellationSignal = CancellationSignal()
         withContext(mainDispatcher) {
             // We always want to invoke face detect in the main thread.
             faceAuthLogger.faceDetectionStarted()
-            faceManager?.detectFace(
-                checkNotNull(detectCancellationSignal),
-                detectionCallback,
-                SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
-                    .toFaceAuthenticateOptions()
-            )
+            detectCancellationSignal?.cancel()
+            detectCancellationSignal = CancellationSignal()
+            detectCancellationSignal?.let {
+                faceManager?.detectFace(
+                    it,
+                    detectionCallback,
+                    SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
+                        .toFaceAuthenticateOptions()
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f637..ecf78d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
 
 /**
  * Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@
 class DeviceEntrySideFpsOverlayInteractor
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@
 
     init {
         if (!DeviceEntryUdfpsRefactor.isEnabled) {
-            alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+            applicationScope.launch {
+                deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+                    if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+                        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+                    }
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 9fe5c3f..cecc653 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,7 +18,7 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7882a97..3888345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,7 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.withContext
+import com.android.app.tracing.coroutines.withContext
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 64ff3b0c..1277585 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,11 +32,8 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
 
 /**
  * Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -68,8 +65,6 @@
          * in the range of [0, 1]. View animations should begin and end within a subset of this
          * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
          * valid.
-         *
-         * Will produce a [SharedFlow], so that identical animations can use the same value.
          */
         fun sharedFlow(
             duration: Duration,
@@ -80,7 +75,7 @@
             onFinish: (() -> Float)? = null,
             interpolator: Interpolator = LINEAR,
             name: String? = null
-        ): SharedFlow<Float> {
+        ): Flow<Float> {
             if (!duration.isPositive()) {
                 throw IllegalArgumentException("duration must be a positive number: $duration")
             }
@@ -137,7 +132,6 @@
                     value
                 }
                 .filterNotNull()
-                .shareIn(scope, SharingStarted.WhileSubscribed())
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206..96e83b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@
                                 vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 settingsMenu.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = settingsMenu,
                                         viewModel = viewModel.settingsMenuViewModel,
                                     )
                                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 7d290c3..05fe0b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -32,8 +32,6 @@
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
-private val TAG = KeyguardClockViewBinder::class.simpleName
-
 object KeyguardClockViewBinder {
     @JvmStatic
     fun bind(
@@ -74,12 +72,6 @@
                         applyConstraints(clockSection, keyguardRootView, true)
                     }
                 }
-                launch {
-                    if (!migrateClocksToBlueprint()) return@launch
-                    viewModel.hasCustomWeatherDataDisplay.collect {
-                        applyConstraints(clockSection, keyguardRootView, true)
-                    }
-                }
             }
         }
     }
@@ -132,7 +124,7 @@
     fun applyConstraints(
         clockSection: ClockSection,
         rootView: ConstraintLayout,
-        animated: Boolean
+        animated: Boolean,
     ) {
         val constraintSet = ConstraintSet().apply { clone(rootView) }
         clockSection.applyConstraints(constraintSet)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 362e7e6..fad0370 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -255,6 +255,7 @@
                                 vibratorHelper.performHapticFeedback(
                                     view,
                                     HapticFeedbackConstants.CONFIRM,
+                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
                                 )
                             }
                         }
@@ -264,6 +265,7 @@
                                 vibratorHelper.performHapticFeedback(
                                     view,
                                     HapticFeedbackConstants.REJECT,
+                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
                                 )
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c..c6dfcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
 
 class KeyguardSettingsButtonOnTouchListener(
-    private val view: LaunchableLinearLayout,
     private val viewModel: KeyguardSettingsMenuViewModel,
 ) : View.OnTouchListener {
 
@@ -41,8 +39,10 @@
             MotionEvent.ACTION_UP -> {
                 view.isPressed = false
                 val distanceMoved =
-                    motionEvent
-                        .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+                    motionEvent.rawDistanceFrom(
+                        downPositionDisplayCoords.x,
+                        downPositionDisplayCoords.y
+                    )
                 val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
                 viewModel.onTouchGestureEnded(isClick)
                 if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e7..f67cb68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@
 
 object KeyguardSettingsViewBinder {
     fun bind(
-        parentView: View,
+        view: View,
         viewModel: KeyguardSettingsMenuViewModel,
         longPressViewModel: KeyguardLongPressViewModel,
-        rootViewModel: KeyguardRootViewModel,
+        rootViewModel: KeyguardRootViewModel?,
         vibratorHelper: VibratorHelper,
         activityStarter: ActivityStarter
     ): DisposableHandle {
-        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@
                                 vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 view.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = view,
                                         viewModel = viewModel,
                                     )
                                 )
@@ -96,7 +92,7 @@
                     }
 
                     launch {
-                        rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+                        rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
                             if (view.isVisible) {
                                 val hitRect = Rect()
                                 view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 954d2cf..e36819b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,13 +16,19 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.transition.TransitionManager
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.launch
 
 object KeyguardSmartspaceViewBinder {
     @JvmStatic
@@ -30,15 +36,63 @@
         smartspaceSection: SmartspaceSection,
         keyguardRootView: ConstraintLayout,
         clockViewModel: KeyguardClockViewModel,
+        smartspaceViewModel: KeyguardSmartspaceViewModel,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                clockViewModel.hasCustomWeatherDataDisplay.collect {
-                    val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
-                    smartspaceSection.applyConstraints(constraintSet)
-                    constraintSet.applyTo(keyguardRootView)
+                launch {
+                    clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
+                        ->
+                        if (hasCustomWeatherDataDisplay) {
+                            removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+                        } else {
+                            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+                        }
+                        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+                        val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
+                        smartspaceSection.applyConstraints(constraintSet)
+                        TransitionManager.beginDelayedTransition(keyguardRootView)
+                        constraintSet.applyTo(keyguardRootView)
+                    }
                 }
             }
         }
     }
+
+    private fun addDateWeatherToBurnInLayer(
+        constraintLayout: ConstraintLayout,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            if (
+                smartspaceViewModel.isSmartspaceEnabled &&
+                    smartspaceViewModel.isDateWeatherDecoupled
+            ) {
+                val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
+                val weatherView =
+                    constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
+                addView(weatherView)
+                addView(dateView)
+            }
+        }
+    }
+
+    private fun removeDateWeatherToBurnInLayer(
+        constraintLayout: ConstraintLayout,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            if (
+                smartspaceViewModel.isSmartspaceEnabled &&
+                    smartspaceViewModel.isDateWeatherDecoupled
+            ) {
+                val dateView = smartspaceViewModel.dateView
+                val weatherView = smartspaceViewModel.weatherView
+                removeView(weatherView)
+                removeView(dateView)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240df..940d1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@
             return true
         }
 
+        if (renderer == null || onDestroy == null) {
+            Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+            return true
+        }
+
         when (message.what) {
             KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                 message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index f890ae6..5344696b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -30,7 +30,10 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
@@ -59,6 +62,9 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+    smartspaceSection: SmartspaceSection,
+    clockSection: SplitShadeClockSection,
+    mediaSection: SplitShadeMediaSection,
 ) : KeyguardBlueprint {
     override val id: String = ID
 
@@ -73,8 +79,11 @@
             splitShadeNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
+            smartspaceSection,
             aodBurnInSection,
             communalTutorialIndicatorSection,
+            clockSection,
+            mediaSection,
             defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8166b45..d89e1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -23,7 +23,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -38,7 +37,6 @@
     private val context: Context,
     private val clockViewModel: KeyguardClockViewModel,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
-    private val featureFlags: FeatureFlagsClassic,
 ) : KeyguardSection() {
     lateinit var burnInLayer: Layer
 
@@ -59,6 +57,8 @@
                 }
             }
         if (migrateClocksToBlueprint()) {
+            // weather and date parts won't be added here, cause their visibility doesn't align
+            // with others in burnInLayer
             addSmartspaceViews(constraintLayout)
         }
         constraintLayout.addView(burnInLayer)
@@ -89,14 +89,6 @@
                 val smartspaceView =
                     constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId)
                 addView(smartspaceView)
-                if (smartspaceViewModel.isDateWeatherDecoupled) {
-                    val dateView =
-                        constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
-                    val weatherView =
-                        constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
-                    addView(weatherView)
-                    addView(dateView)
-                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 39a0547..b429ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -28,7 +28,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -49,7 +48,6 @@
 constructor(
     private val context: Context,
     private val configurationState: ConfigurationState,
-    private val featureFlags: FeatureFlagsClassic,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -119,14 +117,8 @@
             }
         constraintSet.apply {
             if (migrateClocksToBlueprint()) {
-                connect(
-                    nicId,
-                    TOP,
-                    smartspaceViewModel.smartspaceViewId,
-                    topAlignment,
-                    bottomMargin
-                )
-                setGoneMargin(nicId, topAlignment, bottomMargin)
+                connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin)
+                setGoneMargin(nicId, BOTTOM, bottomMargin)
             } else {
                 connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 1df920a..656c75c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -27,7 +27,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -50,19 +50,21 @@
     alpha: Float,
 ) = views.forEach { view -> this.setAlpha(view.id, alpha) }
 
-class ClockSection
+open class ClockSection
 @Inject
 constructor(
     private val clockInteractor: KeyguardClockInteractor,
-    private val keyguardClockViewModel: KeyguardClockViewModel,
-    val smartspaceViewModel: KeyguardSmartspaceViewModel,
+    protected val keyguardClockViewModel: KeyguardClockViewModel,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
-    private val featureFlags: FeatureFlagsClassic,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
     override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!Flags.migrateClocksToBlueprint()) {
+            return
+        }
         KeyguardClockViewBinder.bind(
             this,
             constraintLayout,
@@ -72,6 +74,9 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!Flags.migrateClocksToBlueprint()) {
+            return
+        }
         clockInteractor.clock?.let { clock ->
             constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
         }
@@ -94,16 +99,6 @@
         }
     }
 
-    var largeClockEndGuideline = PARENT_ID
-
-    // Return if largeClockEndGuideline changes,
-    // and use it to decide whether to refresh blueprint
-    fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean {
-        val previousValue = largeClockEndGuideline
-        largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline
-        return previousValue != largeClockEndGuideline
-    }
-
     private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
         if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock)
         else getSmallClockFace(clock)
@@ -113,10 +108,10 @@
 
     private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
     private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
-    fun applyDefaultConstraints(constraints: ConstraintSet) {
+    open fun applyDefaultConstraints(constraints: ConstraintSet) {
         constraints.apply {
             connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END)
+            connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
             connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
             var largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 368b388..8cd51cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
-import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -36,7 +35,7 @@
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 
-class SmartspaceSection
+open class SmartspaceSection
 @Inject
 constructor(
     val keyguardClockViewModel: KeyguardClockViewModel,
@@ -45,9 +44,13 @@
     val smartspaceController: LockscreenSmartspaceController,
     val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
 ) : KeyguardSection() {
-    private var smartspaceView: View? = null
-    private var weatherView: View? = null
-    private var dateView: View? = null
+    var smartspaceView by keyguardSmartspaceViewModel::smartspaceView
+    var weatherView by keyguardSmartspaceViewModel::weatherView
+    var dateView by keyguardSmartspaceViewModel::dateView
+
+    val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId
+    val weatherViewId = keyguardSmartspaceViewModel.weatherId
+    val dateViewId = keyguardSmartspaceViewModel.dateId
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
@@ -67,10 +70,21 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel)
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+        KeyguardSmartspaceViewBinder.bind(
+            this,
+            constraintLayout,
+            keyguardClockViewModel,
+            keyguardSmartspaceViewModel,
+        )
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
         // Generally, weather should be next to dateView
         // smartspace should be below date & weather views
         constraintSet.apply {
@@ -130,7 +144,20 @@
                     }
                 }
             }
-            updateVisibility(constraintSet)
+        }
+        updateVisibility(constraintSet)
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+        listOf(smartspaceView, dateView, weatherView).forEach {
+            it?.let {
+                if (it.parent == constraintLayout) {
+                    constraintLayout.removeView(it)
+                }
+            }
         }
     }
 
@@ -158,14 +185,4 @@
             }
         }
     }
-
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        listOf(smartspaceView, dateView, weatherView).forEach {
-            it?.let {
-                if (it.parent == constraintLayout) {
-                    constraintLayout.removeView(it)
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
new file mode 100644
index 0000000..1302bfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import javax.inject.Inject
+
+class SplitShadeClockSection
+@Inject
+constructor(
+    clockInteractor: KeyguardClockInteractor,
+    keyguardClockViewModel: KeyguardClockViewModel,
+    smartspaceViewModel: KeyguardSmartspaceViewModel,
+    context: Context,
+    splitShadeStateController: SplitShadeStateController,
+) :
+    ClockSection(
+        clockInteractor,
+        keyguardClockViewModel,
+        smartspaceViewModel,
+        context,
+        splitShadeStateController
+    ) {
+    override fun applyDefaultConstraints(constraints: ConstraintSet) {
+        super.applyDefaultConstraints(constraints)
+        val largeClockEndGuideline =
+            if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+            else R.id.split_shade_guideline
+        constraints.apply {
+            connect(
+                R.id.lockscreen_clock_view_large,
+                ConstraintSet.END,
+                largeClockEndGuideline,
+                ConstraintSet.END
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
new file mode 100644
index 0000000..5afdbaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import androidx.constraintlayout.widget.Barrier
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import javax.inject.Inject
+
+/** Aligns media on left side for split shade, below smartspace, date, and weather. */
+class SplitShadeMediaSection
+@Inject
+constructor(
+    private val context: Context,
+    private val notificationPanelView: NotificationPanelView,
+    private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+    private val keyguardMediaController: KeyguardMediaController
+) : KeyguardSection() {
+    private val mediaContainerId = R.id.status_view_media_container
+    private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+
+        notificationPanelView.findViewById<View>(mediaContainerId)?.let {
+            notificationPanelView.removeView(it)
+        }
+
+        val mediaFrame =
+            FrameLayout(context, null).apply {
+                id = mediaContainerId
+                val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+                val horizontalPadding =
+                    padding +
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.status_view_margin_horizontal
+                        )
+
+                setPaddingRelative(horizontalPadding, padding, horizontalPadding, padding)
+            }
+        constraintLayout.addView(mediaFrame)
+        keyguardMediaController.attachSplitShadeContainer(mediaFrame)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {}
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+
+        constraintSet.apply {
+            constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
+            constrainHeight(mediaContainerId, WRAP_CONTENT)
+
+            createBarrier(
+                smartSpaceBarrier,
+                Barrier.BOTTOM,
+                0,
+                *intArrayOf(
+                    keyguardSmartspaceViewModel.smartspaceViewId,
+                    keyguardSmartspaceViewModel.dateId,
+                    keyguardSmartspaceViewModel.weatherId,
+                )
+            )
+            connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+            connect(mediaContainerId, START, PARENT_ID, START)
+            connect(mediaContainerId, END, R.id.split_shade_guideline, END)
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+
+        constraintLayout.removeView(mediaContainerId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index b0b5c81..0f8e673 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,7 +23,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -72,25 +71,9 @@
             return
         }
         constraintSet.apply {
-            val bottomMargin =
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
-            if (migrateClocksToBlueprint()) {
-                connect(
-                    R.id.nssl_placeholder,
-                    TOP,
-                    smartspaceViewModel.smartspaceViewId,
-                    TOP,
-                    bottomMargin
-                )
-                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
-            } else {
-                val splitShadeTopMargin =
-                    context.resources.getDimensionPixelSize(
-                        R.dimen.large_screen_shade_header_height
-                    )
-                connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
-            }
+            val splitShadeTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+            connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
 
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index f4ae365..fa4de04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -20,12 +20,12 @@
 import android.hardware.biometrics.SensorLocationInternal
 import com.android.settingslib.Utils
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.res.R
 import javax.inject.Inject
-import kotlin.math.roundToInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
     val iconLocation: Flow<IconLocation> =
@@ -73,11 +74,7 @@
             .onStart {
                 emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
             }
-    private val fgIconPadding: Flow<Int> =
-        configurationInteractor.scaleForResolution.map { scale ->
-            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
-                .roundToInt()
-        }
+    private val fgIconPadding: Flow<Int> = udfpsOverlayInteractor.iconPadding
     val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
         combine(
             fgIconColor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 3aeff61..528a2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -27,8 +27,8 @@
 import com.android.systemui.plugins.clocks.ClockController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
@@ -79,10 +79,10 @@
                         ?: false
             )
 
-    val clockShouldBeCentered: Flow<Boolean> =
+    val clockShouldBeCentered: StateFlow<Boolean> =
         keyguardInteractor.clockShouldBeCentered.stateIn(
             scope = applicationScope,
             started = SharingStarted.WhileSubscribed(),
-            initialValue = true
+            initialValue = false
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index 4541458..26e7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
@@ -25,7 +26,7 @@
 @SysUISingleton
 class KeyguardSmartspaceViewModel
 @Inject
-constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) {
+constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) {
     val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
     val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
     val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
@@ -38,6 +39,10 @@
     val weatherId: Int
         get() = getId("weather_smartspace_view")
 
+    var smartspaceView: View? = null
+    var weatherView: View? = null
+    var dateView: View? = null
+
     private fun getId(name: String): Int {
         return context.resources.getIdentifier(name, "id", context.packageName).also {
             if (it == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569..36bbe4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@
 constructor(
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
+    val longPress: KeyguardLongPressViewModel,
 ) {
     val isUdfpsVisible: Boolean
         get() = authController.isUdfpsSupported
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 d8bb3e6..0d5ba64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -26,6 +26,7 @@
 import com.android.systemui.log.echo.LogcatEchoTrackerProd;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -417,6 +418,18 @@
     }
 
     /**
+     * Provides a {@link ClockMessageBuffers} which contains the keyguard clock message buffers.
+     */
+    @Provides
+    public static ClockMessageBuffers provideKeyguardClockMessageBuffers(
+            @KeyguardClockLog LogBuffer infraClockLog,
+            @KeyguardSmallClockLog LogBuffer smallClockLog,
+            @KeyguardLargeClockLog LogBuffer largeClockLog
+    ) {
+        return new ClockMessageBuffers(infraClockLog, smallClockLog, largeClockLog);
+    }
+
+    /**
      * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 724241d..185a783 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.pipeline
 
 import android.content.Context
+import android.content.pm.UserInfo
 import android.os.SystemProperties
 import android.util.Log
 import com.android.internal.annotations.KeepForWeakReference
@@ -88,7 +89,11 @@
     private val userTrackerCallback =
         object : UserTracker.Callback {
             override fun onUserChanged(newUser: Int, userContext: Context) {
-                handleUserSwitched(newUser)
+                handleUserSwitched()
+            }
+
+            override fun onProfilesChanged(profiles: List<UserInfo>) {
+                handleProfileChanged()
             }
         }
 
@@ -109,7 +114,10 @@
         }
         allEntries.put(key, data)
 
-        if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
+        if (
+            !lockscreenUserManager.isCurrentProfile(data.userId) ||
+                !lockscreenUserManager.isProfileAvailable(data.userId)
+        ) {
             return
         }
 
@@ -231,7 +239,20 @@
     }
 
     @VisibleForTesting
-    internal fun handleUserSwitched(id: Int) {
+    internal fun handleProfileChanged() {
+        // TODO(b/317221348) re-add media removed when profile is available.
+        allEntries.forEach { (key, data) ->
+            if (!lockscreenUserManager.isProfileAvailable(data.userId)) {
+                // Only remove media when the profile is unavailable.
+                if (DEBUG) Log.d(TAG, "Removing $key after profile change")
+                userEntries.remove(key, data)
+                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal fun handleUserSwitched() {
         // If the user changes, remove all current MediaData objects and inform listeners
         val listenersCopy = listeners
         val keyCopy = userEntries.keys.toMutableList()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 945bf9a..e15e038 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -180,7 +181,11 @@
     /** Called whenever the media hosts visibility changes */
     private fun onMediaHostVisibilityChanged(visible: Boolean) {
         refreshMediaPosition(reason = "onMediaHostVisibilityChanged")
+
         if (visible) {
+            if (migrateClocksToBlueprint() && useSplitShade) {
+                return
+            }
             mediaHost.hostView.layoutParams.apply {
                 height = ViewGroup.LayoutParams.WRAP_CONTENT
                 width = ViewGroup.LayoutParams.MATCH_PARENT
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1e..3e6d46c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@
         logger: MediaTttSenderLogger,
         instanceId: InstanceId,
     ): ChipbarInfo {
-        val packageName = checkNotNull(routeInfo.clientPackageName)
+        val packageName = routeInfo.clientPackageName
         val otherDeviceName =
             if (routeInfo.name.isBlank()) {
                 context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@
             }
         val icon =
             MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
-                logger.logPackageNotFound(packageName)
+                packageName?.let { logger.logPackageNotFound(it) }
             }
 
         val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
index 3c50127..2408af1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -17,23 +17,22 @@
 package com.android.systemui.mediaprojection.taskswitcher
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.pssTaskSwitcher
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import dagger.Lazy
 import javax.inject.Inject
 
 @SysUISingleton
 class MediaProjectionTaskSwitcherCoreStartable
 @Inject
 constructor(
-    private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
-    private val featureFlags: FeatureFlags,
+    private val notificationCoordinatorLazy: Lazy<TaskSwitcherNotificationCoordinator>,
 ) : CoreStartable {
 
     override fun start() {
-        if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
-            notificationCoordinator.start()
+        if (pssTaskSwitcher()) {
+            notificationCoordinatorLazy.get().start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 270bfbe..3aa9daa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -37,7 +37,7 @@
 import android.provider.Settings
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1534653..958ace35 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -48,12 +48,13 @@
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -62,7 +63,10 @@
 import javax.inject.Inject;
 
 @SysUISingleton
-public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
+public class PowerUI implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     static final String TAG = "PowerUI";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -223,7 +227,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
 
         // Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 7184fa0..8dd0ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,14 +16,19 @@
 
 package com.android.systemui.power.dagger;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.power.data.repository.PowerRepositoryModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 
 /** Dagger Module for code in the power package. */
@@ -33,6 +38,17 @@
         }
 )
 public interface PowerModule {
+    /** Starts PowerUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(PowerUI.class)
+    CoreStartable bindPowerUIStartable(PowerUI impl);
+
+    /** Listen to config changes for PowerUI.  */
+    @Binds
+    @IntoSet
+    ConfigurationController.ConfigurationListener bindPowerUIConfigChanges(PowerUI impl);
+
     /** */
     @Binds
     EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index 2345667..83b6f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -19,16 +19,21 @@
 import android.service.quicksettings.IQSTileService;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 
 public class QSTileServiceWrapper {
     private static final String TAG = "IQSTileServiceWrapper";
 
+    @NonNull
     private final IQSTileService mService;
 
-    public QSTileServiceWrapper(IQSTileService service) {
+    public QSTileServiceWrapper(@NonNull IQSTileService service) {
         mService = service;
     }
 
+    // This can never be null, as it's the constructor parameter and it's final
+    @NonNull
     public IBinder asBinder() {
         return mService.asBinder();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index e08eb37..880289e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -40,6 +40,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
@@ -54,8 +55,10 @@
 
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Manages the lifecycle of a TileService.
@@ -101,8 +104,8 @@
     private final ActivityManager mActivityManager;
 
     private Set<Integer> mQueuedMessages = new ArraySet<>();
-    @Nullable
-    private volatile QSTileServiceWrapper mWrapper;
+    @NonNull
+    private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty();
     private boolean mListening;
     private IBinder mClickBinder;
 
@@ -222,6 +225,7 @@
                 // Only try a new binding if we are not currently bound.
                 mIsBound.compareAndSet(false, bindServices());
                 if (!mIsBound.get()) {
+                    Log.d(TAG, "Failed to bind to service");
                     mContext.unbindService(this);
                 }
             } catch (SecurityException e) {
@@ -281,7 +285,7 @@
             service.linkToDeath(this, 0);
         } catch (RemoteException e) {
         }
-        mWrapper = wrapper;
+        mOptionalWrapper = Optional.of(wrapper);
         handlePendingMessages();
     }
 
@@ -368,6 +372,10 @@
      * are supposed to be bound, we will try to bind after some amount of time.
      */
     private void handleDeath() {
+        if (!mIsBound.get()) {
+            // If we are already not bound, don't do anything else.
+            return;
+        }
         mExecutor.execute(() -> {
             if (!mIsBound.get()) {
                 // If we are already not bound, don't do anything else.
@@ -522,7 +530,7 @@
     @Override
     public void onTileAdded() {
         if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
-        if (mWrapper == null || !mWrapper.onTileAdded()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) {
             queueMessage(MSG_ON_ADDED);
             handleDeath();
         }
@@ -531,7 +539,7 @@
     @Override
     public void onTileRemoved() {
         if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
-        if (mWrapper == null || !mWrapper.onTileRemoved()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) {
             queueMessage(MSG_ON_REMOVED);
             handleDeath();
         }
@@ -541,7 +549,7 @@
     public void onStartListening() {
         if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
         mListening = true;
-        if (mWrapper != null && !mWrapper.onStartListening()) {
+        if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) {
             handleDeath();
         }
     }
@@ -550,7 +558,7 @@
     public void onStopListening() {
         if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
         mListening = false;
-        if (mWrapper != null && !mWrapper.onStopListening()) {
+        if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
             handleDeath();
         }
     }
@@ -558,7 +566,7 @@
     @Override
     public void onClick(IBinder iBinder) {
         if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
-        if (mWrapper == null || !mWrapper.onClick(iBinder)) {
+        if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) {
             mClickBinder = iBinder;
             queueMessage(MSG_ON_CLICK);
             handleDeath();
@@ -568,7 +576,7 @@
     @Override
     public void onUnlockComplete() {
         if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
-        if (mWrapper == null || !mWrapper.onUnlockComplete()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) {
             queueMessage(MSG_ON_UNLOCK_COMPLETE);
             handleDeath();
         }
@@ -577,7 +585,7 @@
     @Nullable
     @Override
     public IBinder asBinder() {
-        return mWrapper != null ? mWrapper.asBinder() : null;
+        return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null);
     }
 
     @Override
@@ -591,18 +599,42 @@
     }
 
     private void freeWrapper() {
-        if (mWrapper != null) {
+        if (mOptionalWrapper.isPresent()) {
             try {
-                mWrapper.asBinder().unlinkToDeath(this, 0);
+                mOptionalWrapper.ifPresent(
+                        (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0)
+                );
             } catch (NoSuchElementException e) {
                 Log.w(TAG, "Trying to unlink not linked recipient for component"
                         + mIntent.getComponent().flattenToShortString());
             }
-            mWrapper = null;
+            mOptionalWrapper = Optional.empty();
         }
     }
 
     public interface TileChangeListener {
         void onTileChanged(ComponentName tile);
     }
+
+    /**
+     * Returns true if the Optional is empty OR performing the action on the content of the Optional
+     * (when not empty) fails.
+     */
+    private static boolean isNullOrFailedAction(
+            Optional<QSTileServiceWrapper> optionalWrapper,
+            Predicate<QSTileServiceWrapper> action
+    ) {
+        return !optionalWrapper.map(action::test).orElse(false);
+    }
+
+    /**
+     * Returns true if the Optional is not empty AND performing the action on the content of
+     * the Optional fails.
+     */
+    private static boolean isNotNullAndFailedAction(
+            Optional<QSTileServiceWrapper> optionalWrapper,
+            Predicate<QSTileServiceWrapper> action
+    ) {
+        return  !optionalWrapper.map(action::test).orElse(true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 66da8bd..216d716 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,9 +42,7 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
@@ -63,8 +61,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val sysuiDialogFactory: SystemUIDialog.Factory,
-    private val userContextProvider: UserContextProvider,
+    private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
     QSTileImpl<QSTile.BooleanState>(
         host,
@@ -102,7 +99,8 @@
 
     private fun showPrompt(view: View?) {
         val dialog: AlertDialog =
-            RecordIssueDialogDelegate(sysuiDialogFactory, userContextProvider) {
+            delegateFactory
+                .create {
                     isRecording = true
                     refreshState()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 4b21e44..f071623 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -29,11 +29,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
@@ -57,7 +58,10 @@
  */
 @SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
 @SysUISingleton
-public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+public class RearDisplayDialogController implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     private int[] mFoldedStates;
     private boolean mStartedFolded;
@@ -96,7 +100,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
                 && mDialogViewContainer != null) {
             // Refresh the dialog view when configuration is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
new file mode 100644
index 0000000..6ab294d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.reardisplay
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface RearDisplayModule {
+
+    /** Start RearDisplayDialogController. */
+    @Binds
+    @IntoMap
+    @ClassKey(RearDisplayDialogController::class)
+    abstract fun bindRearDisplayDialogControllerStartable(
+        impl: RearDisplayDialogController
+    ): CoreStartable
+
+    /** Listen to config changes for RearDisplayDialogController. */
+    @Binds
+    @IntoSet
+    fun bindRearDisplayDialogControllerConfigChanges(
+        impl: RearDisplayDialogController
+    ): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f95..4ee65b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -23,13 +23,17 @@
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 
 /**
  * A proxy to a Recents implementation.
  */
-public class Recents implements CoreStartable, CommandQueue.Callbacks {
+public class Recents implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     private final Context mContext;
     private final RecentsImplementation mImpl;
@@ -53,7 +57,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         mImpl.onConfigurationChanged(newConfig);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
index 77a4b9f7..1108917 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -18,14 +18,17 @@
 
 import android.content.Context;
 
-import com.android.systemui.res.R;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 /**
  * Dagger injection module for {@link RecentsImplementation}
@@ -33,6 +36,28 @@
 @Module
 public abstract class RecentsModule {
 
+    /** Start Recents.  */
+    @Binds
+    @IntoMap
+    @ClassKey(Recents.class)
+    abstract CoreStartable bindRecentsStartable(Recents impl);
+
+    /** Listen to config changes for Recents.  */
+    @Binds
+    @IntoSet
+    abstract ConfigurationListener bindRecentsConfigChanges(Recents impl);
+
+    /** Start ScreenPinningRequest.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenPinningRequest.class)
+    abstract CoreStartable bindScreenPinningRequestStartable(ScreenPinningRequest impl);
+
+    /** Listen to config changes for ScreenPinningRequest.  */
+    @Binds
+    @IntoSet
+    abstract ConfigurationListener bindScreenPinningRequestConfigChanges(ScreenPinningRequest impl);
+
     /**
      * @return The {@link RecentsImplementation} from the config.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3e574e7..2b717cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -54,25 +54,29 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.leak.RotationUtils;
 
+import dagger.Lazy;
+
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 @SysUISingleton
-public class ScreenPinningRequest implements View.OnClickListener,
-        NavigationModeController.ModeChangedListener, CoreStartable {
+public class ScreenPinningRequest implements
+        View.OnClickListener,
+        NavigationModeController.ModeChangedListener,
+        CoreStartable,
+        ConfigurationController.ConfigurationListener {
     private static final String TAG = "ScreenPinningRequest";
 
     private final Context mContext;
@@ -149,7 +153,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (mRequestWindow != null) {
             mRequestWindow.onConfigurationChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 5bf44fa..e051df4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -24,27 +24,63 @@
 import android.content.res.ColorStateList
 import android.graphics.Color
 import android.os.Bundle
+import android.os.UserHandle
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.WindowManager
 import android.widget.Button
 import android.widget.PopupMenu
 import android.widget.Switch
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingService
 import com.android.systemui.screenrecord.ScreenRecordingAudioSource
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
 
-class RecordIssueDialogDelegate(
+class RecordIssueDialogDelegate
+@AssistedInject
+constructor(
     private val factory: SystemUIDialog.Factory,
     private val userContextProvider: UserContextProvider,
-    private val onStarted: Runnable
+    private val userTracker: UserTracker,
+    private val flags: FeatureFlagsClassic,
+    @Background private val bgExecutor: Executor,
+    @Main private val mainExecutor: Executor,
+    private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
+    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+    private val userFileManager: UserFileManager,
+    @Assisted private val onStarted: Runnable,
 ) : SystemUIDialog.Delegate {
 
+    /** To inject dependencies and allow for easier testing */
+    @AssistedFactory
+    interface Factory {
+        /** Create a dialog object */
+        fun create(onStarted: Runnable): RecordIssueDialogDelegate
+    }
+
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
     private lateinit var issueTypeButton: Button
 
+    @MainThread
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         dialog.apply {
             setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
@@ -63,17 +99,64 @@
 
     override fun createDialog(): SystemUIDialog = factory.create(this)
 
+    @MainThread
     override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         dialog.apply {
             window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
             window?.setGravity(Gravity.CENTER)
 
             screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+            screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
+                onScreenRecordSwitchClicked(context, isEnabled)
+            }
             issueTypeButton = requireViewById(R.id.issue_type_button)
             issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
         }
     }
 
+    @AnyThread
+    private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
+        if (!isEnabled) return
+
+        bgExecutor.execute {
+            if (
+                flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+                    devicePolicyResolver
+                        .get()
+                        .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+            ) {
+                mainExecutor.execute {
+                    ScreenCaptureDisabledDialog(context).show()
+                    screenRecordSwitch.isChecked = false
+                }
+                return@execute
+            }
+
+            mediaProjectionMetricsLogger.notifyProjectionInitiated(
+                userTracker.userId,
+                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+            )
+
+            if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+                val prefs =
+                    userFileManager.getSharedPreferences(
+                        RecordIssueTile.TILE_SPEC,
+                        Context.MODE_PRIVATE,
+                        userTracker.userId
+                    )
+                if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+                    mainExecutor.execute {
+                        ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+                            setOnCancelListener { screenRecordSwitch.isChecked = false }
+                            show()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @MainThread
     private fun onIssueTypeClicked(context: Context) {
         val selectedCategory = issueTypeButton.text.toString()
         val popupMenu = PopupMenu(context, issueTypeButton)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
new file mode 100644
index 0000000..de6d3f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
+
+class ScreenCapturePermissionDialogDelegate(
+    private val dialogFactory: SystemUIDialog.Factory,
+    private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+
+    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        dialog.apply {
+            setIcon(R.drawable.ic_screenrecord)
+            setTitle(R.string.screenrecord_permission_dialog_title)
+            setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen)
+            setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() }
+            setPositiveButton(R.string.slice_permission_allow) { _, _ ->
+                sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply()
+                dismiss()
+            }
+            window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+            window?.setGravity(Gravity.CENTER)
+        }
+    }
+
+    override fun createDialog(): SystemUIDialog = dialogFactory.create(this)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index a950539..bee3152 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -29,7 +29,7 @@
 import android.view.RemoteAnimationTarget
 import android.view.WindowManager
 import android.view.WindowManagerGlobal
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import com.android.internal.infra.ServiceConnector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index f56f416..3081f89 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,7 +20,7 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import kotlinx.coroutines.CoroutineScope
 import java.util.function.Consumer
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 713ede6..86f6523 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.shade.ShadeExpansionStateManager
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import kotlinx.coroutines.withContext
 
 /** Provides state from the main SystemUI process on behalf of the Screenshot process. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 38d00f7..238a552 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -18,7 +18,7 @@
 
 import android.media.MediaPlayer
 import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.async
+import com.android.app.tracing.coroutines.async
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.google.errorprone.annotations.CanIgnoreReturnValue
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index f6c25e0..e464fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,7 +5,7 @@
 import android.util.Log
 import android.view.Display
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 9f416bb..f2fa0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -135,6 +135,10 @@
         val filter = IntentFilter().apply {
             addAction(Intent.ACTION_LOCALE_CHANGED)
             addAction(Intent.ACTION_USER_INFO_CHANGED)
+            addAction(Intent.ACTION_PROFILE_ADDED)
+            addAction(Intent.ACTION_PROFILE_REMOVED)
+            addAction(Intent.ACTION_PROFILE_AVAILABLE)
+            addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
             // These get called when a managed profile goes in or out of quiet mode.
             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
             addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -157,7 +161,11 @@
             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_ADDED,
             Intent.ACTION_MANAGED_PROFILE_REMOVED,
-            Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
+            Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+            Intent.ACTION_PROFILE_ADDED,
+            Intent.ACTION_PROFILE_REMOVED,
+            Intent.ACTION_PROFILE_AVAILABLE,
+            Intent.ACTION_PROFILE_UNAVAILABLE -> {
                 handleProfilesChanged()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d13edf0..d382b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,8 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
 import android.app.Activity;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -87,6 +89,17 @@
         if (mShadeInteractor.isQsExpanded().getValue()) {
             finish();
         }
+
+        View view = findViewById(R.id.brightness_mirror_container);
+        if (view != null) {
+            collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
+        }
+    }
+
+    private void onShadeStateChange(boolean isQsExpanded) {
+        if (isQsExpanded) {
+            requestFinish();
+        }
     }
 
     private void setWindowAttributes() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
                 mListener.onChanged(mTracking, progress, false);
                 SeekableSliderEventProducer eventProducer =
                         mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null) {
+                if (eventProducer != null && fromUser) {
                     eventProducer.onProgressChanged(seekBar, progress, fromUser);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 878e6fa..286037e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1160,9 +1160,9 @@
         // Occluded->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
                 mOccludedToLockscreenTransition, mMainDispatcher);
-        if (!KeyguardShadeMigrationNssl.isEnabled()) {
-            collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
+        collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
                 setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
             collectFlow(mView,
                     mOccludedToLockscreenTransitionViewModel.getLockscreenTranslationY(),
                     setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1192,8 +1192,10 @@
                 mLockscreenToOccludedTransition, mMainDispatcher);
         collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
                 setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
-        collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
-                setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+        if (!KeyguardShadeMigrationNssl.isEnabled()) {
+            collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
+                    setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+        }
 
         // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
         collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
@@ -1463,6 +1465,9 @@
     }
 
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
+        if (migrateClocksToBlueprint()) {
+            return;
+        }
         mKeyguardMediaController.attachSplitShadeContainer(container);
     }
 
@@ -1720,9 +1725,12 @@
         }
         // To prevent the weather clock from overlapping with the notification shelf on AOD, we use
         // the small clock here
-        if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
-                && hasVisibleNotifications() && isOnAod()) {
-            return SMALL;
+        // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
+        if (!migrateClocksToBlueprint()) {
+            if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
+                    && hasVisibleNotifications() && isOnAod()) {
+                return SMALL;
+            }
         }
         return LARGE;
     }
@@ -1742,8 +1750,9 @@
         } else {
             layout = mNotificationContainerParent;
         }
+
         if (migrateClocksToBlueprint()) {
-            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+            mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
         } else {
             mKeyguardStatusViewController.updateAlignment(
                     layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 93c55de..71efbab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -44,6 +44,13 @@
 
     boolean isCurrentProfile(int userId);
 
+    /**
+     *
+     * @param userId user Id
+     * @return true if user profile is running.
+     */
+    boolean isProfileAvailable(int userId);
+
     /** Adds a listener to be notified when the current user changes. */
     void addUserChangedListener(UserChangedListener listener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 05c3839..633510d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -461,6 +461,13 @@
         }
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public boolean isProfileAvailable(int userId) {
+        synchronized (mLock) {
+            return mUserManager.isUserRunning(userId);
+        }
+    }
+
     private void setShowLockscreenNotifications(boolean show) {
         mShowLockscreenNotifications = show;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 8fe0022..b76cdb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -237,7 +237,7 @@
 
                 // Add and bind.
                 val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
-                for ((idx, notifKey) in toAdd.withIndex()) {
+                for (notifKey in toAdd) {
                     // Lookup the StatusBarIconView from the store.
                     val sbiv = viewStore.iconView(notifKey)
                     if (sbiv == null) {
@@ -256,7 +256,7 @@
                         // added again.
                         removeTransientView(sbiv)
                     }
-                    view.addView(sbiv, idx)
+                    view.addView(sbiv)
                     boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
                     boundViewsByNotifKey[notifKey] =
                         Pair(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index eff91e5..5ee38be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -71,6 +73,20 @@
             KeyguardState.PRIMARY_BOUNCER
         )
 
+    private val lockscreenToOccludedRunning =
+        keyguardTransitionInteractor
+            .transition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
+            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+            .distinctUntilChanged()
+            .onStart { emit(false) }
+
+    private val occludedToLockscreenRunning =
+        keyguardTransitionInteractor
+            .transition(KeyguardState.OCCLUDED, KeyguardState.LOCKSCREEN)
+            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+            .distinctUntilChanged()
+            .onStart { emit(false) }
+
     val shadeCollapseFadeInComplete = MutableStateFlow(false)
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -122,7 +138,11 @@
             ) { isKeyguard, isShadeVisible, qsExpansion ->
                 isKeyguard && !(isShadeVisible || qsExpansion)
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
 
     /** Fade in only for use after the shade collapses */
     val shadeCollpaseFadeIn: Flow<Boolean> =
@@ -182,26 +202,37 @@
             )
 
     val alpha: Flow<Float> =
-        isOnLockscreenWithoutShade
-            .flatMapLatest { isOnLockscreenWithoutShade ->
-                combineTransform(
-                    merge(
-                        occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-                        lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
-                        keyguardInteractor.keyguardAlpha,
-                    ),
-                    shadeCollpaseFadeIn,
-                ) { alpha, shadeCollpaseFadeIn ->
-                    if (isOnLockscreenWithoutShade) {
-                        if (!shadeCollpaseFadeIn) {
-                            emit(alpha)
-                        }
+        // Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
+        // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
+        // is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
+        // those transitions are in progress. Without this, the alpha value will produce a visible
+        // flicker.
+        lockscreenToOccludedRunning.flatMapLatest { isLockscreenToOccludedRunning ->
+            if (isLockscreenToOccludedRunning) {
+                lockscreenToOccludedTransitionViewModel.lockscreenAlpha
+            } else {
+                occludedToLockscreenRunning.flatMapLatest { isOccludedToLockscreenRunning ->
+                    if (isOccludedToLockscreenRunning) {
+                        occludedToLockscreenTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }
                     } else {
-                        emit(1f)
+                        isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
+                            combineTransform(
+                                keyguardInteractor.keyguardAlpha,
+                                shadeCollpaseFadeIn,
+                            ) { alpha, shadeCollpaseFadeIn ->
+                                if (isOnLockscreenWithoutShade) {
+                                    if (!shadeCollpaseFadeIn) {
+                                        emit(alpha)
+                                    }
+                                } else {
+                                    emit(1f)
+                                }
+                            }
+                        }
                     }
                 }
             }
-            .distinctUntilChanged()
+        }
 
     /**
      * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 97cb45a..6e8ad2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -21,13 +21,17 @@
 import android.os.LocaleList
 import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import javax.inject.Inject
 
 @SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController {
+class ConfigurationControllerImpl @Inject constructor(
+        @Application context: Context,
+        ) : ConfigurationController {
 
-    private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+    private val listeners: MutableList<ConfigurationListener> = ArrayList()
     private val lastConfig = Configuration()
     private var density: Int = 0
     private var smallestScreenWidth: Int = 0
@@ -143,14 +147,12 @@
         }
     }
 
-
-
-    override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+    override fun addCallback(listener: ConfigurationListener) {
         listeners.add(listener)
         listener.onDensityOrFontScaleChanged()
     }
 
-    override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+    override fun removeCallback(listener: ConfigurationListener) {
         listeners.remove(listener)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
new file mode 100644
index 0000000..90ebaf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@SysUISingleton
+class ConfigurationControllerStartable
+@Inject
+constructor(
+    private val configurationController: ConfigurationController,
+    private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+) : CoreStartable {
+    override fun start() {
+        listeners.forEach { configurationController.addCallback(it) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
deleted file mode 100644
index 7048a78..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
- */
-public class StatusBarIconHolder {
-    public static final int TYPE_ICON = 0;
-    /**
-     * TODO (b/249790733): address this once the new pipeline is in place
-     * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
-     * to inform the old view system about changes to the data set (the list of mobile icons). The
-     * design of the new pipeline should allow for removal of this icon holder type, and obsolete
-     * the need for this entire class.
-     *
-     * @deprecated This field only exists so the new status bar pipeline can interface with the
-     * view holder system.
-     */
-    @Deprecated
-    public static final int TYPE_MOBILE_NEW = 3;
-
-    /**
-     * TODO (b/238425913): address this once the new pipeline is in place
-     * This type exists so that the new wifi pipeline can be used to inform the old view system
-     * about the existence of the wifi icon. The design of the new pipeline should allow for removal
-     * of this icon holder type, and obsolete the need for this entire class.
-     *
-     * @deprecated This field only exists so the new status bar pipeline can interface with the
-     * view holder system.
-     */
-    @Deprecated
-    public static final int TYPE_WIFI_NEW = 4;
-
-    @IntDef({
-            TYPE_ICON,
-            TYPE_MOBILE_NEW,
-            TYPE_WIFI_NEW
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface IconType {}
-
-    private StatusBarIcon mIcon;
-    private @IconType int mType = TYPE_ICON;
-    private int mTag = 0;
-
-    /** Returns a human-readable string representing the given type. */
-    public static String getTypeString(@IconType int type) {
-        switch(type) {
-            case TYPE_ICON: return "ICON";
-            case TYPE_MOBILE_NEW: return "MOBILE_NEW";
-            case TYPE_WIFI_NEW: return "WIFI_NEW";
-            default: return "UNKNOWN";
-        }
-    }
-
-    private StatusBarIconHolder() {
-    }
-
-    public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
-        StatusBarIconHolder wrapper = new StatusBarIconHolder();
-        wrapper.mIcon = icon;
-
-        return wrapper;
-    }
-
-    /** Creates a new holder with for the new wifi icon. */
-    public static StatusBarIconHolder forNewWifiIcon() {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mType = TYPE_WIFI_NEW;
-        return holder;
-    }
-
-    /**
-     * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
-     * determine icon ordering and building the correct view model
-     */
-    public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mType = TYPE_MOBILE_NEW;
-        holder.mTag = subId;
-
-        return holder;
-    }
-
-    /**
-     * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
-     */
-    public static StatusBarIconHolder fromCallIndicatorState(
-            Context context,
-            CallIndicatorIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
-        String contentDescription = state.isNoCalling
-                ? state.noCallingDescription : state.callStrengthDescription;
-        holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource(context, resId), 0, 0, contentDescription);
-        holder.mTag = state.subId;
-        return holder;
-    }
-
-    public @IconType int getType() {
-        return mType;
-    }
-
-    @Nullable
-    public StatusBarIcon getIcon() {
-        return mIcon;
-    }
-
-    public void setIcon(StatusBarIcon icon) {
-        mIcon = icon;
-    }
-
-    public boolean isVisible() {
-        switch (mType) {
-            case TYPE_ICON:
-                return mIcon.visible;
-            case TYPE_MOBILE_NEW:
-            case TYPE_WIFI_NEW:
-                // The new pipeline controls visibilities via the view model and view binder, so
-                // this is effectively an unused return value.
-                return true;
-            default:
-                return true;
-        }
-    }
-
-    public void setVisible(boolean visible) {
-        if (isVisible() == visible) {
-            return;
-        }
-
-        switch (mType) {
-            case TYPE_ICON:
-                mIcon.visible = visible;
-                break;
-
-            case TYPE_MOBILE_NEW:
-            case TYPE_WIFI_NEW:
-                // The new pipeline controls visibilities via the view model and view binder, so
-                // ignore setVisible.
-                break;
-        }
-    }
-
-    public int getTag() {
-        return mTag;
-    }
-
-    @Override
-    public String toString() {
-        return "StatusBarIconHolder(type=" + getTypeString(mType)
-                + " tag=" + getTag()
-                + " visible=" + isVisible() + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
new file mode 100644
index 0000000..5b55a1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 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 android.annotation.IntDef
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+
+/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
+class StatusBarIconHolder private constructor() {
+    @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+    @Retention(AnnotationRetention.SOURCE)
+    internal annotation class IconType
+
+    var icon: StatusBarIcon? = null
+
+    @IconType
+    var type = TYPE_ICON
+        private set
+
+    var tag = 0
+        private set
+
+    var isVisible: Boolean
+        get() =
+            when (type) {
+                TYPE_ICON -> icon!!.visible
+
+                // The new pipeline controls visibilities via the view model and
+                // view binder, so
+                // this is effectively an unused return value.
+                TYPE_MOBILE_NEW,
+                TYPE_WIFI_NEW -> true
+                else -> true
+            }
+        set(visible) {
+            if (isVisible == visible) {
+                return
+            }
+            when (type) {
+                TYPE_ICON -> icon!!.visible = visible
+                TYPE_MOBILE_NEW,
+                TYPE_WIFI_NEW -> {}
+            }
+        }
+
+    override fun toString(): String {
+        return ("StatusBarIconHolder(type=${getTypeString(type)}" +
+            " tag=$tag" +
+            " visible=$isVisible)")
+    }
+
+    companion object {
+        const val TYPE_ICON = 0
+
+        /**
+         * TODO (b/249790733): address this once the new pipeline is in place This type exists so
+         * that the new pipeline (see [MobileIconViewModel]) can be used to inform the old view
+         * system about changes to the data set (the list of mobile icons). The design of the new
+         * pipeline should allow for removal of this icon holder type, and obsolete the need for
+         * this entire class.
+         */
+        @Deprecated(
+            """This field only exists so the new status bar pipeline can interface with the
+      view holder system."""
+        )
+        const val TYPE_MOBILE_NEW = 3
+
+        /**
+         * TODO (b/238425913): address this once the new pipeline is in place This type exists so
+         * that the new wifi pipeline can be used to inform the old view system about the existence
+         * of the wifi icon. The design of the new pipeline should allow for removal of this icon
+         * holder type, and obsolete the need for this entire class.
+         */
+        @Deprecated(
+            """This field only exists so the new status bar pipeline can interface with the
+      view holder system."""
+        )
+        const val TYPE_WIFI_NEW = 4
+
+        /** Returns a human-readable string representing the given type. */
+        fun getTypeString(@IconType type: Int): String {
+            return when (type) {
+                TYPE_ICON -> "ICON"
+                TYPE_MOBILE_NEW -> "MOBILE_NEW"
+                TYPE_WIFI_NEW -> "WIFI_NEW"
+                else -> "UNKNOWN"
+            }
+        }
+
+        @JvmStatic
+        fun fromIcon(icon: StatusBarIcon?): StatusBarIconHolder {
+            val wrapper = StatusBarIconHolder()
+            wrapper.icon = icon
+            return wrapper
+        }
+
+        /** Creates a new holder with for the new wifi icon. */
+        @JvmStatic
+        fun forNewWifiIcon(): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            holder.type = TYPE_WIFI_NEW
+            return holder
+        }
+
+        /**
+         * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+         * determine icon ordering and building the correct view model
+         */
+        @JvmStatic
+        fun fromSubIdForModernMobileIcon(subId: Int): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            holder.type = TYPE_MOBILE_NEW
+            holder.tag = subId
+            return holder
+        }
+
+        /** Creates a new StatusBarIconHolder from a CallIndicatorIconState. */
+        @JvmStatic
+        fun fromCallIndicatorState(
+            context: Context,
+            state: CallIndicatorIconState,
+        ): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            val resId = if (state.isNoCalling) state.noCallingResId else state.callStrengthResId
+            val contentDescription =
+                if (state.isNoCalling) state.noCallingDescription else state.callStrengthDescription
+            holder.icon =
+                StatusBarIcon(
+                    UserHandle.SYSTEM,
+                    context.packageName,
+                    Icon.createWithResource(context, resId),
+                    0,
+                    0,
+                    contentDescription,
+                )
+            holder.tag = state.subId
+            return holder
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 942d186..b048da492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,12 +16,16 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
+import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 /**
  * Dagger Module providing {@link CentralSurfacesImpl}.
@@ -34,4 +38,12 @@
     @Binds
     @SysUISingleton
     CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
+
+    /**
+     * Starts {@link ConfigurationControllerStartable}
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(ConfigurationControllerStartable.class)
+    CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2740cc6..11456ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -453,11 +453,11 @@
     public void initNotificationIconArea() {
         ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
         if (NotificationIconContainerRefactor.isEnabled()) {
-            mNotificationIconAreaInner =
-                LayoutInflater.from(getContext())
-                        .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+            LayoutInflater.from(getContext())
+                    .inflate(R.layout.notification_icon_area, notificationIconArea, true);
             NotificationIconContainer notificationIcons =
                     notificationIconArea.requireViewById(R.id.notificationIcons);
+            mNotificationIconAreaInner = notificationIcons;
             mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
         } else {
             mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 425da5f..48bf7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -27,6 +27,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -48,7 +49,7 @@
     override val subId: Int,
     startingIsCarrierMerged: Boolean,
     override val tableLogBuffer: TableLogBuffer,
-    subscriptionModel: StateFlow<SubscriptionModel?>,
+    subscriptionModel: Flow<SubscriptionModel?>,
     private val defaultNetworkName: NetworkNameModel,
     private val networkNameSeparator: String,
     @Application scope: CoroutineScope,
@@ -331,7 +332,7 @@
         fun build(
             subId: Int,
             startingIsCarrierMerged: Boolean,
-            subscriptionModel: StateFlow<SubscriptionModel?>,
+            subscriptionModel: Flow<SubscriptionModel?>,
             defaultNetworkName: NetworkNameModel,
             networkNameSeparator: String,
         ): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 4fb99c24..be2c21b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -96,7 +96,7 @@
 class MobileConnectionRepositoryImpl(
     override val subId: Int,
     private val context: Context,
-    subscriptionModel: StateFlow<SubscriptionModel?>,
+    subscriptionModel: Flow<SubscriptionModel?>,
     defaultNetworkName: NetworkNameModel,
     networkNameSeparator: String,
     connectivityManager: ConnectivityManager,
@@ -448,7 +448,7 @@
         fun build(
             subId: Int,
             mobileLogger: TableLogBuffer,
-            subscriptionModel: StateFlow<SubscriptionModel?>,
+            subscriptionModel: Flow<SubscriptionModel?>,
             defaultNetworkName: NetworkNameModel,
             networkNameSeparator: String,
         ): MobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2a510e4..a455db2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -357,10 +357,10 @@
 
     @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
 
-    private fun subscriptionModelForSubId(subId: Int): StateFlow<SubscriptionModel?> {
-        return subscriptions
-            .map { list -> list.firstOrNull { model -> model.subscriptionId == subId } }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+    private fun subscriptionModelForSubId(subId: Int): Flow<SubscriptionModel?> {
+        return subscriptions.map { list ->
+            list.firstOrNull { model -> model.subscriptionId == subId }
+        }
     }
 
     private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
new file mode 100644
index 0000000..6cd9993
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ToastModule {
+    /** Starts ToastUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(ToastUI::class)
+    fun bindToastUIStartable(service: ToastUI): CoreStartable
+
+    /** Listen to config changes for ToastUI. */
+    @Binds @IntoSet fun bindToastUIConfigChanges(service: ToastUI): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 27f8121..85a455d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -42,6 +42,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.util.Objects;
 
@@ -51,7 +52,10 @@
  * Controls display of text toasts.
  */
 @SysUISingleton
-public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
+public class ToastUI implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
     // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
     private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
     private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
@@ -187,7 +191,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (newConfig.orientation != mOrientation) {
             mOrientation = newConfig.orientation;
             if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 472f0ae..cf6b0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -5,9 +5,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dagger.qualifiers.Tracing
-import com.android.systemui.Flags.coroutineTracing
-import com.android.app.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
-import com.android.app.tracing.TraceContextElement
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.CoroutineDispatcher
@@ -16,7 +14,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.plus
 import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
 
 /** Providers for various coroutines-related constructs. */
 @Module
@@ -83,9 +80,6 @@
     @Tracing
     @SysUISingleton
     fun tracingCoroutineContext(): CoroutineContext {
-        return if (coroutineTracing()) {
-            coroutineTracingIsEnabled = true
-            TraceContextElement()
-        } else EmptyCoroutineContext
+        return createCoroutineTracingContext()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 3451ae0..dc2b80c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -22,16 +22,17 @@
 import android.util.Log;
 
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
 @SysUISingleton
-public class VolumeUI implements CoreStartable {
+public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener {
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -60,7 +61,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (!mEnabled) return;
         mVolumeComponent.onConfigurationChanged(newConfig);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 53217d4..8d06a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@
 import android.os.Looper;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
@@ -36,15 +37,30 @@
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
+import com.android.systemui.volume.VolumeUI;
 
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 /** Dagger Module for code in the volume package. */
 @Module
 public interface VolumeModule {
+    /** Starts VolumeUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumeUI.class)
+    CoreStartable bindVolumeUIStartable(VolumeUI impl);
+
+    /** Listen to config changes for VolumeUI. */
+    @Binds
+    @IntoSet
+    ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
+
     /** */
     @Binds
     VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6f58bc2..e6637e6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -30,13 +30,15 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.plugins.clocks.ClockAnimations
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockEvents
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockTickRate
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -94,9 +96,9 @@
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
     private lateinit var repository: FakeKeyguardRepository
-    @Mock private lateinit var smallLogBuffer: LogBuffer
-    @Mock private lateinit var largeLogBuffer: LogBuffer
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+    private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
     private lateinit var underTest: ClockEventController
     @Mock private lateinit var zenModeController: ZenModeController
 
@@ -140,8 +142,7 @@
                 context,
                 mainExecutor,
                 bgExecutor,
-                smallLogBuffer,
-                largeLogBuffer,
+                clockBuffers,
                 withDeps.featureFlags,
                 zenModeController
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 639276e..b3eab8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -248,8 +248,8 @@
             }
 
             @Override
-            public void onConfigurationChanged(Configuration newConfig) {
-                super.onConfigurationChanged(newConfig);
+            public void onConfigChanged(Configuration newConfig) {
+                super.onConfigChanged(newConfig);
                 mExecutor.runAllReady();
             }
 
@@ -892,7 +892,7 @@
         // Switch to long edge cutout(left).
         mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
     }
 
@@ -913,7 +913,7 @@
         // Switch to long edge cutout(left).
         mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
         verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
         verify(mDotViewController, times(2)).setShowingListener(null);
@@ -949,7 +949,7 @@
         // top cutout
         mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
 
         // Only top windows should be added.
         verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
@@ -976,7 +976,7 @@
         // top cutout
         mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
 
         // Both top and bottom windows should be added with VISIBLE because of privacy dot and
         // cutout, but rounded corners visibility shall be gone because of no rounding.
@@ -1013,7 +1013,7 @@
         doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
-        mScreenDecorations.onConfigurationChanged(null);
+        mScreenDecorations.onConfigChanged(null);
 
         assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
         assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
@@ -1145,7 +1145,7 @@
         assertThat(mScreenDecorations.mIsRegistered, is(false));
 
         doReturn(true).when(mScreenDecorations).hasOverlays();
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1156,7 +1156,7 @@
         mScreenDecorations.start();
         assertThat(mScreenDecorations.mIsRegistered, is(true));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1168,7 +1168,7 @@
         assertThat(mScreenDecorations.mIsRegistered, is(true));
 
         doReturn(false).when(mScreenDecorations).hasOverlays();
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index f8856c9..bd49927 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -102,8 +102,9 @@
                 getContext().getMainThreadHandler(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
-        mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
-                mContext.getSystemService(DisplayManager.class));
+        mMagnification.mWindowMagnificationControllerSupplier =
+                new FakeWindowMagnificationControllerSupplier(
+                        mContext.getSystemService(DisplayManager.class));
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
                 mContext.getSystemService(DisplayManager.class));
 
@@ -201,10 +202,10 @@
         verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
     }
 
-    private class FakeControllerSupplier extends
+    private class FakeWindowMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
-        FakeControllerSupplier(DisplayManager displayManager) {
+        FakeWindowMagnificationControllerSupplier(DisplayManager displayManager) {
             super(displayManager);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index d0e1678..3b5cbea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -124,7 +124,7 @@
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
                 getContext().getSystemService(DisplayManager.class), mA11yLogger);
-        mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+        mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
                 mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
@@ -325,9 +325,9 @@
     @Test
     public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
         final WindowMagnificationController mController = mock(WindowMagnificationController.class);
-        mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+        mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mController);
-        mMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
+        mMagnification.mWindowMagnificationControllerSupplier.get(TEST_DISPLAY);
 
         mOverviewProxyListener.onConnectionChanged(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e3..9bcab57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
-    private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private DismissView mDismissView;
 
     @Rule
@@ -65,19 +65,20 @@
                 stubMenuViewAppearance);
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, stubMenuView);
     }
 
     @Test
     public void showDismissView_success() {
-        mDismissAnimationController.showDismissView(true);
+        mDragToInteractAnimationController.showDismissView(true);
 
         verify(mDismissView).show();
     }
 
     @Test
     public void hideDismissView_success() {
-        mDismissAnimationController.showDismissView(false);
+        mDragToInteractAnimationController.showDismissView(false);
 
         verify(mDismissView).hide();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05..9c8de30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@
     @Mock
     private SecureSettings mSecureSettings;
     @Mock
-    private DismissAnimationController.DismissCallback mStubDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
 
     private RecyclerView mStubListView;
     private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2a..e1522f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@
     private MenuView mStubMenuView;
     private MenuListViewTouchHandler mTouchHandler;
     private MenuAnimationController mMenuAnimationController;
-    private DismissAnimationController mDismissAnimationController;
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private RecyclerView mStubListView;
     private DismissView mDismissView;
 
@@ -92,10 +92,10 @@
                 mStubMenuView, stubMenuViewAppearance));
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController =
-                spy(new DismissAnimationController(mDismissView, mStubMenuView));
+        mDragToInteractAnimationController =
+                spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
         mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
         mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@
 
     @Test
     public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
-        doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+        doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
                 any(MotionEvent.class));
         final int offset = 100;
         final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index e2aa984..647dae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,10 +20,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import org.junit.Assert.assertFalse
 import org.junit.Before
@@ -42,8 +41,7 @@
 
     @Mock lateinit var udfpsBpView: UdfpsBpView
     @Mock lateinit var statusBarStateController: StatusBarStateController
-    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
-    @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock lateinit var dumpManager: DumpManager
 
@@ -55,7 +53,7 @@
             UdfpsBpViewController(
                 udfpsBpView,
                 statusBarStateController,
-                primaryBouncerInteractor,
+                shadeInteractor,
                 systemUIDialogManager,
                 dumpManager
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00d..42d2c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@
 
         deviceEntrySideFpsOverlayInteractor =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
                 primaryBouncerInteractor,
@@ -260,14 +261,14 @@
             SideFpsOverlayViewBinder(
                 testScope.backgroundScope,
                 mContext,
-                biometricStatusInteractor,
-                displayStateInteractor,
-                deviceEntrySideFpsOverlayInteractor,
-                fpsUnlockTracker,
-                layoutInflater,
-                sideFpsProgressBarViewModel,
-                sfpsSensorInteractor,
-                windowManager
+                { biometricStatusInteractor },
+                { displayStateInteractor },
+                { deviceEntrySideFpsOverlayInteractor },
+                { fpsUnlockTracker },
+                { layoutInflater },
+                { sideFpsProgressBarViewModel },
+                { sfpsSensorInteractor },
+                { windowManager }
             )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdc..983e4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@
 
         deviceEntrySideFpsOverlayInteractor =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
                 primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 16b2ed6..9c5cd71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -140,21 +140,76 @@
             }
             assertThat(widgets())
                 .containsExactly(
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
                     communalItemRankEntry1,
                     communalWidgetItemEntry1,
-                    communalItemRankEntry2,
+                )
+                .inOrder()
+
+            // swapped priorities
+            val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
+            communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+            assertThat(widgets())
+                .containsExactly(
+                    communalItemRankEntry1.copy(rank = 2),
+                    communalWidgetItemEntry1,
+                    communalItemRankEntry2.copy(rank = 1),
                     communalWidgetItemEntry2
                 )
+                .inOrder()
+        }
 
-            val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
-            communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+    @Test
+    fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
+        testScope.runTest {
+            val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+            val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+            existingWidgets.forEach {
+                val (widgetId, provider, priority) = it
+                communalWidgetDao.addWidget(
+                    widgetId = widgetId,
+                    provider = provider,
+                    priority = priority,
+                )
+            }
             assertThat(widgets())
                 .containsExactly(
                     communalItemRankEntry2,
                     communalWidgetItemEntry2,
                     communalItemRankEntry1,
-                    communalWidgetItemEntry1
+                    communalWidgetItemEntry1,
                 )
+                .inOrder()
+
+            // map with no item in the middle at index 1
+            val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
+            communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
+            assertThat(widgets())
+                .containsExactly(
+                    communalItemRankEntry2.copy(rank = 3),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry1.copy(rank = 1),
+                    communalWidgetItemEntry1,
+                )
+                .inOrder()
+            // add the new middle item that we left space for.
+            communalWidgetDao.addWidget(
+                widgetId = widgetInfo3.widgetId,
+                provider = widgetInfo3.provider,
+                priority = 2,
+            )
+            assertThat(widgets())
+                .containsExactly(
+                    communalItemRankEntry2.copy(rank = 3),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3.copy(rank = 2),
+                    communalWidgetItemEntry3,
+                    communalItemRankEntry1.copy(rank = 1),
+                    communalWidgetItemEntry1,
+                )
+                .inOrder()
         }
 
     data class FakeWidgetMetadata(
@@ -176,8 +231,15 @@
                 provider = ComponentName("pk_name", "cls_name_2"),
                 priority = 2
             )
+        val widgetInfo3 =
+            FakeWidgetMetadata(
+                widgetId = 3,
+                provider = ComponentName("pk_name", "cls_name_3"),
+                priority = 3
+            )
         val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
         val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+        val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
         val communalWidgetItemEntry1 =
             CommunalWidgetItem(
                 uid = 1L,
@@ -192,5 +254,12 @@
                 componentName = widgetInfo2.provider.flattenToString(),
                 itemId = communalItemRankEntry2.uid,
             )
+        val communalWidgetItemEntry3 =
+            CommunalWidgetItem(
+                uid = 3L,
+                widgetId = widgetInfo3.widgetId,
+                componentName = widgetInfo3.provider.flattenToString(),
+                itemId = communalItemRankEntry3.uid,
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
 
             assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
         }
+
+    @Test
+    fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStartTrackingTouch(seekBar)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+    }
+
+    @Test
+    fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, false)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
         verifyNoMoreInteractions(sliderStateListener)
     }
 
+    @Test
+    fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        initTracker(testScheduler)
+
+        // GIVEN a progress due to an external source that lands at the middle of the slider
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+        verify(sliderStateListener).onSelectAndArrow(progress)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // GIVEN a progress due to an external source that lands at the upper bookend
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving back to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // WHEN a progress is recorded due to an external source that lands at the lower bookend
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves back to IDLE and there are no haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider gets an external progress change
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves to IDLE and no haptics are played
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves to WAIT and the wait job starts.
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider changes progress programmatically at the middle
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker stays in the same state and haptics are delivered appropriately
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun initTracker(
         scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 0616a34..027dfa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -109,6 +109,7 @@
             )
         underTest =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 FakeDeviceEntryFingerprintAuthRepository(),
                 primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index e89b61f..dc0d9c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -20,7 +20,6 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -43,7 +42,6 @@
     @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
     @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
-    private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
 
     private lateinit var underTest: ClockSection
 
@@ -88,7 +86,6 @@
                 smartspaceViewModel,
                 mContext,
                 splitShadeStateController,
-                featureFlags
             )
     }
 
@@ -147,24 +144,6 @@
         assetSmallClockTop(cs, expectedSmallClockTopMargin)
     }
 
-    @Test
-    fun testLargeClockShouldBeCentered() {
-        underTest.setClockShouldBeCentered(true)
-        val cs = ConstraintSet()
-        underTest.applyDefaultConstraints(cs)
-        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
-        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
-    fun testLargeClockShouldNotBeCentered() {
-        underTest.setClockShouldBeCentered(false)
-        val cs = ConstraintSet()
-        underTest.applyDefaultConstraints(cs)
-        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
-        assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
-    }
-
     private fun setLargeClock(useLargeClock: Boolean) {
         whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index bff27f6..740110b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -69,14 +69,13 @@
                 keyguardUnlockAnimationController,
             )
         constraintLayout = ConstraintLayout(mContext)
-        whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout))
-            .thenReturn(smartspaceView)
-        whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout))
-            .thenReturn(weatherView)
-        whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout))
-            .thenReturn(dateView)
+        whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView)
+        whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView)
+        whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView)
         whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
             .thenReturn(hasCustomWeatherDataDisplay)
+        whenever(keyguardSmartspaceViewModel.smartspaceController)
+            .thenReturn(lockscreenSmartspaceController)
         constraintSet = ConstraintSet()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 459a74c..ee1be10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 8532ffe..94b9fa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -55,6 +55,7 @@
 private const val KEY_ALT = "TEST_KEY_2"
 private const val USER_MAIN = 0
 private const val USER_GUEST = 10
+private const val PRIVATE_PROFILE = 12
 private const val PACKAGE = "PKG"
 private val INSTANCE_ID = InstanceId.fakeInstanceId(123)!!
 private const val APP_UID = 99
@@ -82,6 +83,7 @@
     private lateinit var mediaDataFilter: MediaDataFilter
     private lateinit var dataMain: MediaData
     private lateinit var dataGuest: MediaData
+    private lateinit var dataPrivateProfile: MediaData
     private val clock = FakeSystemClock()
 
     @Before
@@ -115,6 +117,7 @@
                 appUid = APP_UID
             )
         dataGuest = dataMain.copy(userId = USER_GUEST)
+        dataPrivateProfile = dataMain.copy(userId = PRIVATE_PROFILE)
 
         whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
         whenever(smartspaceData.isActive).thenReturn(true)
@@ -130,8 +133,19 @@
 
     private fun setUser(id: Int) {
         whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+        whenever(lockscreenUserManager.isProfileAvailable(anyInt())).thenReturn(false)
         whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
-        mediaDataFilter.handleUserSwitched(id)
+        whenever(lockscreenUserManager.isProfileAvailable(eq(id))).thenReturn(true)
+        whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(true)
+        mediaDataFilter.handleUserSwitched()
+    }
+
+    private fun setPrivateProfileUnavailable() {
+        whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+        whenever(lockscreenUserManager.isCurrentProfile(eq(USER_MAIN))).thenReturn(true)
+        whenever(lockscreenUserManager.isCurrentProfile(eq(PRIVATE_PROFILE))).thenReturn(true)
+        whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(false)
+        mediaDataFilter.handleProfileChanged()
     }
 
     @Test
@@ -206,6 +220,20 @@
     }
 
     @Test
+    fun testOnProfileChanged_profileUnavailable_loadControls() {
+        // GIVEN that we had some media for both profiles
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+        mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
+        reset(listener)
+
+        // and we change profile status
+        setPrivateProfileUnavailable()
+
+        // THEN we should add the private profile media
+        verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+    }
+
+    @Test
     fun hasAnyMedia_noMediaSet_returnsFalse() {
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
index bcbf666..16c92ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -18,38 +18,27 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_PSS_TASK_SWITCHER
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
-import com.android.systemui.util.mockito.whenever
-import org.junit.Before
+import com.android.systemui.util.mockito.mock
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
 
-    @Mock private lateinit var flags: FeatureFlags
-    @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+    private val coordinator = mock<TaskSwitcherNotificationCoordinator>()
 
-    private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
-    }
+    private val coreStartable =
+        MediaProjectionTaskSwitcherCoreStartable(notificationCoordinatorLazy = { coordinator })
 
     @Test
     fun start_flagEnabled_startsCoordinator() {
-        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+        mSetFlagsRule.enableFlags(FLAG_PSS_TASK_SWITCHER)
 
         coreStartable.start()
 
@@ -58,7 +47,7 @@
 
     @Test
     fun start_flagDisabled_doesNotStartCoordinator() {
-        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+        mSetFlagsRule.disableFlags(FLAG_PSS_TASK_SWITCHER)
 
         coreStartable.start()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 9b61447..c7479fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,9 +65,9 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
-    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
+    @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
     @Mock private lateinit var dialog: SystemUIDialog
-    @Mock private lateinit var userContextProvider: UserContextProvider
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: RecordIssueTile
@@ -76,7 +76,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(dialogFactory.create(any())).thenReturn(dialog)
+        whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
+        whenever(dialogDelegate.createDialog()).thenReturn(dialog)
 
         testableLooper = TestableLooper.get(this)
         tile =
@@ -93,8 +94,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
-                dialogFactory,
-                userContextProvider,
+                delegateFactory,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index c108a80..273ce85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -28,8 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -82,7 +82,7 @@
         TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
                 R.id.rear_display_title_text_view);
 
-        controller.onConfigurationChanged(new Configuration());
+        controller.onConfigChanged(new Configuration());
         assertTrue(controller.mRearDisplayEducationDialog.isShowing());
         TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
                 R.id.rear_display_title_text_view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index c5d3524..7ce51ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.recordissue
 
 import android.app.Dialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.widget.Button
@@ -25,48 +28,107 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
 import com.android.systemui.model.SysUiState
+import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class RecordIssueDialogDelegateTest : SysuiTestCase() {
 
+    @Mock private lateinit var flags: FeatureFlagsClassic
+    @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
+    @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
+    @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
+    @Mock private lateinit var userContextProvider: UserContextProvider
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userFileManager: UserFileManager
+    @Mock private lateinit var sharedPreferences: SharedPreferences
+
+    @Mock private lateinit var sysuiState: SysUiState
+    @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var bgExecutor: Executor
+    @Mock private lateinit var mainExecutor: Executor
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
     private lateinit var dialog: SystemUIDialog
+    private lateinit var factory: SystemUIDialog.Factory
     private lateinit var latch: CountDownLatch
 
     @Before
     fun setup() {
-        val dialogFactory =
-            SystemUIDialog.Factory(
-                context,
-                mock<FeatureFlags>(),
-                mock<SystemUIDialogManager>(),
-                mock<SysUiState>().apply {
-                    whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
-                },
-                mock<BroadcastDispatcher>(),
-                mock<DialogLaunchAnimator>()
+        MockitoAnnotations.initMocks(this)
+        whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
+        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+        whenever(userContextProvider.userContext).thenReturn(mContext)
+        whenever(
+                userFileManager.getSharedPreferences(
+                    eq(RecordIssueTile.TILE_SPEC),
+                    eq(Context.MODE_PRIVATE),
+                    anyInt()
+                )
+            )
+            .thenReturn(sharedPreferences)
+
+        factory =
+            spy(
+                SystemUIDialog.Factory(
+                    context,
+                    flags,
+                    systemUIDialogManager,
+                    sysuiState,
+                    broadcastDispatcher,
+                    dialogLaunchAnimator
+                )
             )
 
         latch = CountDownLatch(1)
         dialog =
-            RecordIssueDialogDelegate(dialogFactory, mock()) { latch.countDown() }.createDialog()
+            RecordIssueDialogDelegate(
+                    factory,
+                    userContextProvider,
+                    userTracker,
+                    flags,
+                    bgExecutor,
+                    mainExecutor,
+                    dprLazy,
+                    mediaProjectionMetricsLogger,
+                    userFileManager,
+                ) {
+                    latch.countDown()
+                }
+                .createDialog()
         dialog.show()
     }
 
@@ -91,4 +153,82 @@
         dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
         latch.await(1L, TimeUnit.MILLISECONDS)
     }
+
+    @Test
+    fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(true)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(true)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(mainExecutor).execute(mainCaptor.capture())
+        mainCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger, never())
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        assertThat(screenRecordSwitch.isChecked).isFalse()
+    }
+
+    @Test
+    fun screenCapturePermissionDialog_isShown_correctly() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(false)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(false)
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+        whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false))
+            .thenReturn(false)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(mainExecutor).execute(mainCaptor.capture())
+        mainCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger)
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        verify(factory).create(any<ScreenCapturePermissionDialogDelegate>())
+    }
+
+    @Test
+    fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(false)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(false)
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger)
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index c32d259..032ec74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -177,7 +177,7 @@
         verify(context)
                 .registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
         with(captor.value) {
-            assertThat(countActions()).isEqualTo(7)
+            assertThat(countActions()).isEqualTo(11)
             assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
             assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
@@ -185,6 +185,10 @@
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+            assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+            assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+            assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+            assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 88c728f..b94e483 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -36,8 +36,12 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -59,7 +63,6 @@
     @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
     @Mock private lateinit var brightnessController: BrightnessController
     @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
-    @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
     @Mock private lateinit var shadeInteractor: ShadeInteractor
 
     private val clock = FakeSystemClock()
@@ -89,7 +92,6 @@
             .thenReturn(brightnessSliderController)
         `when`(brightnessSliderController.rootView).thenReturn(View(context))
         `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
-        whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
         whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
     }
 
@@ -180,6 +182,22 @@
         assertThat(activityRule.activity.isFinishing()).isFalse()
     }
 
+    @OptIn(FlowPreview::class)
+    @Test
+    fun testFinishOnQSExpanded() = runTest {
+        val isQSExpanded = MutableStateFlow(false)
+        `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        isQSExpanded.value = true
+        // Observe the activity's state until is it finishing or the timeout is reached, whatever
+        // comes first. This fixes the flakiness seen when using advanceUntilIdle().
+        activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
     class TestDialog(
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
         brightnessControllerFactory: BrightnessController.Factory,
@@ -194,14 +212,14 @@
             accessibilityMgr,
             shadeInteractor
         ) {
-        private var finishing = false
+        var finishing = MutableStateFlow(false)
 
         override fun isFinishing(): Boolean {
-            return finishing
+            return finishing.value
         }
 
         override fun requestFinish() {
-            finishing = true
+            finishing.value = true
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ee94cbb..ee27c5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.flags.Flags.TRANSIT_CLOCK
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
@@ -128,6 +129,7 @@
         override fun createClock(settings: ClockSettings): ClockController =
             createCallbacks[settings.clockId!!]!!(settings.clockId!!)
         override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+        override fun initialize(buffers: ClockMessageBuffers?) { }
 
         fun addClock(
             id: ClockId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index fef262f..e0e8d1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.customization.R
+import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
 import com.android.systemui.util.mockito.any
@@ -49,6 +50,9 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
+private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController =
+    createClock(ClockSettings(id, null)) as DefaultClockController
+
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class DefaultClockProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1..50349be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@
         testScope.runTest {
             val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
 
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = 1,
             )
             assertThat(showNotifs).isEqualTo(true)
 
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = 0,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 6eabf44..bc50c25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -17,13 +17,17 @@
 package com.android.systemui.shared.plugins;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -40,7 +44,11 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -104,6 +112,7 @@
         mPluginInstance = mPluginInstanceFactory.create(
                 mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
                 TestPlugin.class, mPluginListener);
+        mPluginInstance.setIsDebug(true);
         mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
@@ -158,7 +167,7 @@
 
     @Test
     public void testOnAttach_SkipLoad() {
-        mPluginListener.mAttachReturn = false;
+        mPluginListener.mOnAttach = () -> false;
         mPluginInstance.onCreate();
         assertEquals(1, mPluginListener.mAttachedCount);
         assertEquals(0, mPluginListener.mLoadCount);
@@ -166,6 +175,65 @@
         assertInstances(0, 0);
     }
 
+    @Test
+    public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+        final Semaphore loadLock = new Semaphore(1);
+        final Semaphore unloadLock = new Semaphore(1);
+
+        mPluginListener.mOnAttach = () -> false;
+        mPluginListener.mOnLoad = () -> {
+            assertNotNull(mPluginInstance.getPlugin());
+
+            // Allow the bg thread the opportunity to delete the plugin
+            loadLock.release();
+            Thread.yield();
+            boolean isLocked = getLock(unloadLock, 1000);
+
+            // Ensure the bg thread failed to do delete the plugin
+            assertNotNull(mPluginInstance.getPlugin());
+            // We expect that bgThread deadlocked holding the semaphore
+            assertFalse(isLocked);
+        };
+
+        AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+        Thread bgThread = new Thread(() -> {
+            assertTrue(getLock(unloadLock, 10));
+            assertTrue(getLock(loadLock, 4000)); // Wait for the foreground thread
+            assertNotNull(mPluginInstance.getPlugin());
+            // Attempt to delete the plugin, this should block until the load completes
+            mPluginInstance.unloadPlugin();
+            assertNull(mPluginInstance.getPlugin());
+            unloadLock.release();
+            loadLock.release();
+        });
+
+        // This protects the test suite from crashing due to the uncaught exception.
+        bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
+            Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
+            isBgThreadFailed.set(true);
+        });
+
+        loadLock.acquire();
+        mPluginInstance.onCreate();
+
+        assertNull(mPluginInstance.getPlugin());
+        bgThread.start();
+        mPluginInstance.loadPlugin();
+
+        bgThread.join(5000);
+        assertFalse(isBgThreadFailed.get());
+        assertNull(mPluginInstance.getPlugin());
+    }
+
+    private boolean getLock(Semaphore lock, long millis) {
+        try {
+            return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ex) {
+            fail();
+            return false;
+        }
+    }
+
     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
     // the mock version info is called.
     @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -226,7 +294,10 @@
     }
 
     public class FakeListener implements PluginListener<TestPlugin> {
-        public boolean mAttachReturn = true;
+        public Supplier<Boolean> mOnAttach = null;
+        public Runnable mOnDetach = null;
+        public Runnable mOnLoad = null;
+        public Runnable mOnUnload = null;
         public int mAttachedCount = 0;
         public int mDetachedCount = 0;
         public int mLoadCount = 0;
@@ -236,13 +307,16 @@
         public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
             mAttachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
-            return mAttachReturn;
+            return mOnAttach != null ? mOnAttach.get() : true;
         }
 
         @Override
         public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
             mDetachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnDetach != null) {
+                mOnDetach.run();
+            }
         }
 
         @Override
@@ -261,6 +335,9 @@
                 assertEquals(expectedContext, pluginContext);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnLoad != null) {
+                mOnLoad.run();
+            }
         }
 
         @Override
@@ -274,6 +351,9 @@
                 assertEquals(expectedPlugin, plugin);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnUnload != null) {
+                mOnUnload.run();
+            }
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 2bee7b8..d3febf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
 import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -151,6 +152,7 @@
 
     @Test
     public void testOnConnectReadStatusBarSetting() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
         NotificationListener.NotificationSettingsListener settingsListener =
                 mock(NotificationListener.NotificationSettingsListener.class);
         mListener.addNotificationSettingsListener(settingsListener);
@@ -164,6 +166,7 @@
 
     @Test
     public void testOnStatusBarIconsBehaviorChanged() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
         NotificationListener.NotificationSettingsListener settingsListener =
                 mock(NotificationListener.NotificationSettingsListener.class);
         mListener.addNotificationSettingsListener(settingsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 2df6e46..d4300f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -17,6 +17,7 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
@@ -71,6 +72,7 @@
 
     @Test
     fun testShadeWidth_BasedOnFractionToShade() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(true)
 
@@ -86,6 +88,7 @@
 
     @Test
     fun testShelfIsLong_WhenNotOnLockscreen() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7558974..1236fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -38,6 +38,7 @@
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -84,6 +85,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
         initController(/* viewIsAttached= */ true);
         mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee3..83ba684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@
 
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
@@ -81,6 +83,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+        mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -319,7 +324,9 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -328,10 +335,14 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -385,8 +396,8 @@
         mStackScroller.setExpandedHeight(100f);
     }
 
-
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void manageNotifications_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void clearAll_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testInflateFooterView() {
         mStackScroller.inflateFooterView();
         ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -459,7 +472,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
     }
 
     @Test
@@ -474,7 +487,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
     }
 
     @Test
@@ -490,7 +503,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, false);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
     }
 
     @Test
@@ -505,7 +518,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -521,7 +534,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
     }
 
     @Test
@@ -529,7 +542,8 @@
         mStackScroller.setCurrentUserSetup(true);
 
         // add footer
-        mStackScroller.inflateFooterView();
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
 
         // add notification
         ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@
     }
 
     @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testReInflatesEmptyShadeView() {
+        when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+        clearInvocations(mStackScroller);
+        mStackScroller.reinflateViews();
+        verify(mStackScroller, never()).setFooterView(any());
+        verify(mStackScroller).setEmptyShadeView(any());
+    }
+
+    @Test
     public void testSetIsBeingDraggedResetsExposedMenu() {
         mStackScroller.setIsBeingDragged(true);
         verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@
 
     @Test
     public void testClearNotifications_clearAllInProgress() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         ExpandableNotificationRow row = createClearableRow();
         when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
         doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@
 
     @Test
     public void testAddNotificationUpdatesSpeedBumpIndex() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -661,6 +690,8 @@
 
     @Test
     public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated  == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -677,6 +708,8 @@
 
     @Test
     public void testRemoveNotificationUpdatesSpeedBump() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -872,6 +905,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateFooter() {
         mStackScroller.setCurrentUserSetup(true);
 
@@ -887,6 +921,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
         mStackScroller.setHasFilteredOutSeenNotifications(true);
         mStackScroller.updateEmptyShadeView(true, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 36a4712..20020f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -418,13 +419,13 @@
         }
 
     @Test
-    fun shadeCollpaseFadeIn() =
+    fun shadeCollapseFadeIn() =
         testScope.runTest {
+            val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
+
             // Start on lockscreen without the shade
             underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-
-            val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
             assertThat(fadeIn).isEqualTo(false)
 
             // ... then the shade expands
@@ -440,10 +441,12 @@
             assertThat(fadeIn).isEqualTo(false)
         }
 
-    private suspend fun showLockscreen() {
+    private suspend fun TestScope.showLockscreen() {
         shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(0f)
+        runCurrent()
         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+        runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.AOD,
             to = KeyguardState.LOCKSCREEN,
@@ -451,10 +454,12 @@
         )
     }
 
-    private suspend fun showLockscreenWithShadeExpanded() {
+    private suspend fun TestScope.showLockscreenWithShadeExpanded() {
         shadeRepository.setLockscreenShadeExpansion(1f)
         shadeRepository.setQsExpansion(0f)
+        runCurrent()
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+        runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.AOD,
             to = KeyguardState.LOCKSCREEN,
@@ -462,10 +467,12 @@
         )
     }
 
-    private suspend fun showLockscreenWithQSExpanded() {
+    private suspend fun TestScope.showLockscreenWithQSExpanded() {
         shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(1f)
+        runCurrent()
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+        runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.AOD,
             to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 62d8f7f..9f4e1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -73,6 +74,7 @@
 
     @Test
     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
         iconContainer.setIconSize(10)
@@ -151,7 +153,7 @@
         iconContainer.addView(iconFive)
         assertEquals(5, iconContainer.childCount)
 
-        val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
+        val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
         iconContainer.setActualLayoutWidth(width.toInt())
 
         iconContainer.calculateIconXTranslations()
@@ -212,6 +214,7 @@
 
     @Test
     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         val forceOverflow =
             iconContainer.shouldForceOverflow(
                 /* i= */ 1,
@@ -228,7 +231,7 @@
             iconContainer.shouldForceOverflow(
                 /* i= */ 10,
                 /* speedBumpIndex= */ 11,
-                /* iconAppearAmount= */ 0f,
+                /* iconAppearAmount= */ 0.1f,
                 /* maxVisibleIcons= */ 5
             )
         assertTrue(forceOverflow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 7594c90..feff046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.graphics.Point
+import android.testing.TestableLooper
 import android.view.Display
 import android.view.Surface
 import android.view.View
@@ -19,6 +20,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@TestableLooper.RunWithLooper
 class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 14751c2..54d3607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -36,6 +36,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -91,7 +92,6 @@
 
     private NotificationIconAreaController mMockNotificationAreaController;
     private ShadeExpansionStateManager mShadeExpansionStateManager;
-    private View mNotificationAreaInner;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
     private StatusBarLocationPublisher mLocationPublisher;
@@ -270,15 +270,15 @@
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -310,7 +310,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -326,7 +326,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -343,7 +343,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the shade is updated to no longer be open
@@ -354,7 +354,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -368,7 +368,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -382,7 +382,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -396,7 +396,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the transition has finished
@@ -405,7 +405,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -438,7 +438,7 @@
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -503,8 +503,8 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, true);
 
         // Notification area is hidden without delay
-        assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01);
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -723,11 +723,10 @@
 
     private void setUpNotificationIconAreaController() {
         mMockNotificationAreaController = mock(NotificationIconAreaController.class);
-
-        mNotificationAreaInner = new View(mContext);
-
-        when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn(
-                mNotificationAreaInner);
+        View notificationAreaInner =
+                LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
+        when(mMockNotificationAreaController.getNotificationInnerAreaView())
+                .thenReturn(notificationAreaInner);
     }
 
     /**
@@ -782,4 +781,8 @@
     private View getEndSideContentView() {
         return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
     }
+
+    private View getNotificationAreaView() {
+        return mFragment.getView().findViewById(R.id.notificationIcons);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index e461e3f..bbc96f70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -26,8 +26,11 @@
 
     private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
     private var currentRecording: UnfoldTransitionRecording? = null
+    var lastCallbackThread: Thread? = null
+        private set
 
     override fun onTransitionStarted() {
+        lastCallbackThread = Thread.currentThread()
         assertWithMessage("Trying to start a transition when it is already in progress")
             .that(currentRecording)
             .isNull()
@@ -36,6 +39,7 @@
     }
 
     override fun onTransitionProgress(progress: Float) {
+        lastCallbackThread = Thread.currentThread()
         assertWithMessage("Received transition progress event when it's not started")
             .that(currentRecording)
             .isNotNull()
@@ -43,6 +47,7 @@
     }
 
     override fun onTransitionFinishing() {
+        lastCallbackThread = Thread.currentThread()
         assertWithMessage("Received transition finishing event when it's not started")
             .that(currentRecording)
             .isNotNull()
@@ -50,6 +55,7 @@
     }
 
     override fun onTransitionFinished() {
+        lastCallbackThread = Thread.currentThread()
         assertWithMessage("Received transition finish event when it's not started")
             .that(currentRecording)
             .isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index a25469b..d864d53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.unfold.util
 
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@TestableLooper.RunWithLooper
 class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
 
     @Mock lateinit var rotationChangeProvider: RotationChangeProvider
@@ -48,10 +50,12 @@
     @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
 
     lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+    private lateinit var testableLooper : TestableLooper
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
 
         progressProvider =
             NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
@@ -123,5 +127,6 @@
 
     private fun onRotationChanged(rotation: Int) {
         rotationListenerCaptor.value.onRotationChanged(rotation)
+        testableLooper.processAllMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index e1e54a9..2f29b3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -19,6 +19,7 @@
 import android.database.ContentObserver
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -36,6 +37,7 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@TestableLooper.RunWithLooper
 class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
 
     @Mock lateinit var sinkProvider: TransitionProgressListener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
new file mode 100644
index 0000000..5b4f4d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.util
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Process
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.progress.TestUnfoldProgressListener
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withTimeout
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
+
+    private val rootProvider = TestUnfoldTransitionProvider()
+    private val listener = TestUnfoldProgressListener()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val bgThread =
+        HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() }
+    private val bgHandler = Handler(bgThread.looper)
+    private val scopedProvider =
+        ScopedUnfoldTransitionProgressProvider(rootProvider).apply { addCallback(listener) }
+
+    @Test
+    fun setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks() =
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+
+            scopedProvider.setReadyToHandleTransition(true)
+
+            runBlockingInBg { /* sync barrier */}
+
+            listener.assertStarted()
+
+            runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+            listener.assertLastProgress(1f)
+
+            runBlockingInBg { rootProvider.onTransitionFinished() }
+
+            listener.assertNotStarted()
+        }
+
+    @Test
+    fun setReadyToHandleTransition_whileTransitionNotRunning_callbacksInProgressThread() {
+        testScope.runTest {
+            scopedProvider.setReadyToHandleTransition(true)
+
+            val bgThread = runBlockingInBg { Thread.currentThread() }
+
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+
+            listener.assertStarted()
+
+            assertThat(listener.lastCallbackThread).isEqualTo(bgThread)
+        }
+    }
+
+    @Test
+    fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
+        testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
+    }
+
+    @Test
+    fun onTransitionStarted_whileNotReadyToHandleTransition_doesNotPropagate() {
+        testScope.runTest {
+            scopedProvider.setReadyToHandleTransition(false)
+
+            rootProvider.onTransitionStarted()
+
+            listener.assertNotStarted()
+        }
+    }
+
+    @Test
+    fun onTransitionStarted_defaultReadiness_doesNotPropagate() {
+        testScope.runTest {
+            rootProvider.onTransitionStarted()
+
+            listener.assertNotStarted()
+        }
+    }
+
+    @Test
+    fun onTransitionStarted_fromDifferentThreads_throws() {
+        testScope.runTest {
+            runBlockingInBg {
+                rootProvider.onTransitionStarted()
+                rootProvider.onTransitionFinished()
+            }
+            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionStarted() }
+        }
+    }
+
+    @Test
+    fun onTransitionProgress_fromDifferentThreads_throws() {
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+            assertThrows(IllegalStateException::class.java) {
+                rootProvider.onTransitionProgress(1f)
+            }
+        }
+    }
+
+    @Test
+    fun onTransitionFinished_fromDifferentThreads_throws() {
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinished() }
+        }
+    }
+
+    @Test
+    fun onTransitionFinishing_fromDifferentThreads_throws() {
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinishing() }
+        }
+    }
+
+    private fun <T> runBlockingInBg(f: () -> T): T {
+        return runBlocking {
+            withTimeout(5.seconds) {
+                suspendCancellableCoroutine { c: CancellableContinuation<T> ->
+                    bgHandler.post { c.resumeWith(Result.success(f())) }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index 4a38fc0..f484ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.unfold.util
 
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -27,6 +28,7 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@TestableLooper.RunWithLooper
 class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
 
     private val listener = TestUnfoldProgressListener()
@@ -54,9 +56,7 @@
         sourceProvider.onTransitionProgress(0.5f)
         sourceProvider.onTransitionFinished()
 
-        with(listener.ensureTransitionFinished()) {
-            assertLastProgress(0.5f)
-        }
+        with(listener.ensureTransitionFinished()) { assertLastProgress(0.5f) }
     }
 
     @Test
@@ -121,8 +121,6 @@
         sourceProvider.onTransitionProgress(0.1f)
         sourceProvider.onTransitionFinished()
 
-        with(listener.ensureTransitionFinished()) {
-            assertLastProgress(0.1f)
-        }
+        with(listener.ensureTransitionFinished()) { assertLastProgress(0.1f) }
     }
 }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce..e52cefb 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,4 +39,7 @@
     sdk_version: "current",
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index f9751d9..2bca272 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -15,8 +15,11 @@
  */
 package com.android.systemui.unfold.util
 
+import android.os.Handler
+import android.os.Looper
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.util.concurrent.CopyOnWriteArrayList
 
 /**
  * Manages progress listeners that can have smaller lifespan than the unfold animation.
@@ -33,12 +36,13 @@
 constructor(source: UnfoldTransitionProgressProvider? = null) :
     UnfoldTransitionProgressProvider, TransitionProgressListener {
 
+    private var progressHandler: Handler? = null
     private var source: UnfoldTransitionProgressProvider? = null
 
-    private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+    private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
 
-    private var isReadyToHandleTransition = false
-    private var isTransitionRunning = false
+    @Volatile private var isReadyToHandleTransition = false
+    @Volatile private var isTransitionRunning = false
     private var lastTransitionProgress = PROGRESS_UNSET
 
     init {
@@ -70,15 +74,18 @@
      * Call it with readyToHandleTransition = false when listeners can't process the events.
      */
     fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
-        if (isTransitionRunning) {
-            if (isReadyToHandleTransition) {
-                listeners.forEach { it.onTransitionStarted() }
-                if (lastTransitionProgress != PROGRESS_UNSET) {
-                    listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+        val progressHandler = this.progressHandler
+        if (isTransitionRunning && progressHandler != null) {
+            progressHandler.post {
+                if (isReadyToHandleTransition) {
+                    listeners.forEach { it.onTransitionStarted() }
+                    if (lastTransitionProgress != PROGRESS_UNSET) {
+                        listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+                    }
+                } else {
+                    isTransitionRunning = false
+                    listeners.forEach { it.onTransitionFinished() }
                 }
-            } else {
-                isTransitionRunning = false
-                listeners.forEach { it.onTransitionFinished() }
             }
         }
         this.isReadyToHandleTransition = isReadyToHandleTransition
@@ -98,6 +105,7 @@
     }
 
     override fun onTransitionStarted() {
+        assertInProgressThread()
         isTransitionRunning = true
         if (isReadyToHandleTransition) {
             listeners.forEach { it.onTransitionStarted() }
@@ -105,6 +113,7 @@
     }
 
     override fun onTransitionProgress(progress: Float) {
+        assertInProgressThread()
         if (isReadyToHandleTransition) {
             listeners.forEach { it.onTransitionProgress(progress) }
         }
@@ -112,12 +121,14 @@
     }
 
     override fun onTransitionFinishing() {
+        assertInProgressThread()
         if (isReadyToHandleTransition) {
             listeners.forEach { it.onTransitionFinishing() }
         }
     }
 
     override fun onTransitionFinished() {
+        assertInProgressThread()
         if (isReadyToHandleTransition) {
             listeners.forEach { it.onTransitionFinished() }
         }
@@ -125,6 +136,21 @@
         lastTransitionProgress = PROGRESS_UNSET
     }
 
+    private fun assertInProgressThread() {
+        val cachedProgressHandler = progressHandler
+        if (cachedProgressHandler == null) {
+            val thisLooper = Looper.myLooper() ?: error("This thread is expected to have a looper.")
+            progressHandler = Handler(thisLooper)
+        } else {
+            check(cachedProgressHandler.looper.isCurrentThread) {
+                """Receiving unfold transition callback from different threads.
+                    |Current: ${Thread.currentThread()}
+                    |expected: ${cachedProgressHandler.looper.thread}"""
+                    .trimMargin()
+            }
+        }
+    }
+
     companion object {
         private const val PROGRESS_UNSET = -1f
     }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fca..491ed22 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@
 android.os.Parcel
 android.os.Parcelable
 android.os.Process
+android.os.ServiceSpecificException
 android.os.SystemClock
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a4b2896..cab2d74 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.appwidget;
 
+import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -144,6 +145,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -277,7 +279,12 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
         mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mSaveStateHandler = BackgroundThread.getHandler();
+        if (removeAppWidgetServiceIoFromCriticalPath()) {
+            mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
+                    this::handleSaveMessage);
+        } else {
+            mSaveStateHandler = BackgroundThread.getHandler();
+        }
         final ServiceThread serviceThread = new ServiceThread(TAG,
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
         serviceThread.start();
@@ -314,6 +321,40 @@
         mMaxWidgetBitmapMemory = 6 * size.x * size.y;
     }
 
+    private boolean handleSaveMessage(Message msg) {
+        final int userId = msg.what;
+        SparseArray<byte[]> userIdToBytesMapping;
+        synchronized (mLock) {
+            // No need to enforce unlocked state when there is no caller. User can be in the
+            // stopping state or removed by the time the message is processed
+            ensureGroupStateLoadedLocked(userId, false /* enforceUserUnlockingOrUnlocked */);
+            userIdToBytesMapping = saveStateToByteArrayLocked(userId);
+        }
+
+        for (int i = 0; i < userIdToBytesMapping.size(); i++) {
+            int currentProfileId = userIdToBytesMapping.keyAt(i);
+            byte[] currentStateByteArray = userIdToBytesMapping.valueAt(i);
+            AtomicFile currentFile = getSavedStateFile(currentProfileId);
+            FileOutputStream fileStream;
+            try {
+                fileStream = currentFile.startWrite();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to start writing stream", e);
+                continue;
+            }
+
+            try {
+                fileStream.write(currentStateByteArray);
+                currentFile.finishWrite(fileStream);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to write state byte stream to file", e);
+                currentFile.failWrite(fileStream);
+            }
+        }
+
+        return true;
+    }
+
     private void registerBroadcastReceiver() {
         // Register for broadcasts about package install, etc., so we can
         // update the provider list.
@@ -1944,7 +1985,12 @@
     }
 
     private void saveGroupStateAsync(int groupId) {
-        mSaveStateHandler.post(new SaveStateRunnable(groupId));
+        if (removeAppWidgetServiceIoFromCriticalPath()) {
+            mSaveStateHandler.removeMessages(groupId);
+            mSaveStateHandler.sendEmptyMessage(groupId);
+        } else {
+            mSaveStateHandler.post(new SaveStateRunnable(groupId));
+        }
     }
 
     private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
@@ -3104,6 +3150,23 @@
     }
 
     @GuardedBy("mLock")
+    private @NonNull SparseArray<byte[]> saveStateToByteArrayLocked(int userId) {
+        tagProvidersAndHosts();
+
+        final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+        SparseArray<byte[]> userIdToBytesMapping = new SparseArray<>();
+
+        for (int profileId : profileIds) {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            if (writeProfileStateToStreamLocked(outputStream, profileId)) {
+                userIdToBytesMapping.put(profileId, outputStream.toByteArray());
+            }
+        }
+
+        return userIdToBytesMapping;
+    }
+
+    @GuardedBy("mLock")
     private void saveStateLocked(int userId) {
         tagProvidersAndHosts();
 
@@ -3117,7 +3180,7 @@
             FileOutputStream stream;
             try {
                 stream = file.startWrite();
-                if (writeProfileStateToFileLocked(stream, profileId)) {
+                if (writeProfileStateToStreamLocked(stream, profileId)) {
                     file.finishWrite(stream);
                 } else {
                     file.failWrite(stream);
@@ -3158,7 +3221,7 @@
     }
 
     @GuardedBy("mLock")
-    private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
+    private boolean writeProfileStateToStreamLocked(OutputStream stream, int userId) {
         int N;
 
         try {
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index d9741c8..4a6d5c9b 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,9 +25,6 @@
 import android.service.autofill.FillResponse;
 import android.util.Slog;
 
-import java.util.Objects;
-
-
 /**
  * Requests autofill response from a Remote Autofill Service. This autofill service can be
  * either a Credential Autofill Service or the user-opted autofill service.
@@ -51,7 +48,6 @@
 
     private final RemoteFillService mRemoteFillService;
     private final SecondaryProviderCallback mCallback;
-    private FillRequest mLastFillRequest;
     private int mLastFlag;
 
     SecondaryProviderHandler(
@@ -97,17 +93,11 @@
     }
 
     /**
-     * Requests a new fill response. If the fill request is same as the last requested fill request,
-     * then the request is duped.
+     * Requests a new fill response.
      */
     public void onFillRequest(FillRequest pendingFillRequest, int flag) {
-        if (Objects.equals(pendingFillRequest, mLastFillRequest)) {
-            Slog.v(TAG, "Deduping fill request to secondary provider.");
-            return;
-        }
         Slog.v(TAG, "Requesting fill response to secondary provider.");
         mLastFlag = flag;
-        mLastFillRequest = pendingFillRequest;
         mRemoteFillService.onFillRequest(pendingFillRequest);
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d527ce0..c4e8f12 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -367,6 +367,9 @@
     @GuardedBy("mLock")
     private SparseArray<FillResponse> mResponses;
 
+    @GuardedBy("mLock")
+    private SparseArray<FillResponse> mSecondaryResponses;
+
     /**
      * Contexts read from the app; they will be updated (sanitized, change values for save) before
      * sent to {@link AutofillService}. Ordered by the time they were read.
@@ -713,7 +716,14 @@
                         mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
-            mRemoteFillService.onFillRequest(mPendingFillRequest);
+            if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
+                    && mSecondaryProviderHandler != null) {
+                Slog.v(TAG, "Requesting fill response to secondary provider.");
+                mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
+                        mPendingFillRequest.getFlags());
+            } else if (mRemoteFillService != null) {
+                mRemoteFillService.onFillRequest(mPendingFillRequest);
+            }
             mPendingInlineSuggestionsRequest = null;
             mWaitForInlineRequest = false;
             mPendingFillRequest = null;
@@ -1196,7 +1206,8 @@
     @GuardedBy("mLock")
     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
             int flags) {
-        final FillResponse existingResponse = viewState.getResponse();
+        final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+                ? viewState.getSecondaryResponse() : viewState.getResponse();
         mFillRequestEventLogger.startLogForNewRequest();
         mRequestCount++;
         mFillRequestEventLogger.maybeSetAppPackageUid(uid);
@@ -1804,6 +1815,10 @@
             return;
         }
         synchronized (mLock) {
+            if (mSecondaryResponses == null) {
+                mSecondaryResponses = new SparseArray<>(2);
+            }
+            mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse);
             setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
                     /* isPrimary= */ false);
 
@@ -3980,7 +3995,7 @@
         }
 
         // If it's not, then check if it should start a partition.
-        if (shouldStartNewPartitionLocked(id)) {
+        if (shouldStartNewPartitionLocked(id, flags)) {
             if (sDebug) {
                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
                         + viewState.getStateAsString());
@@ -4008,9 +4023,11 @@
      * @return {@code true} if a new partition should be started
      */
     @GuardedBy("mLock")
-    private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
+    private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) {
         final ViewState currentView = mViewStates.get(id);
-        if (mResponses == null) {
+        SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags)
+                ? mSecondaryResponses : mResponses;
+        if (responses == null) {
             return currentView != null && (currentView.getState()
                     & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
         }
@@ -4022,7 +4039,7 @@
             return true;
         }
 
-        final int numResponses = mResponses.size();
+        final int numResponses = responses.size();
         if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
             Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
                     + " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
@@ -4030,7 +4047,7 @@
         }
 
         for (int responseNum = 0; responseNum < numResponses; responseNum++) {
-            final FillResponse response = mResponses.valueAt(responseNum);
+            final FillResponse response = responses.valueAt(responseNum);
 
             if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
                 return false;
@@ -4066,6 +4083,10 @@
     }
 
     boolean shouldRequestSecondaryProvider(int flags) {
+        if (!mService.isAutofillCredmanIntegrationEnabled()
+                || mSecondaryProviderHandler == null) {
+            return false;
+        }
         if (mIsPrimaryCredential) {
             return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0;
         } else {
@@ -4205,12 +4226,6 @@
                 }
                 break;
             case ACTION_VIEW_ENTERED:
-                if (shouldRequestSecondaryProvider(flags)
-                        && mSecondaryProviderHandler != null
-                        && mAssistReceiver.mLastFillRequest != null) {
-                    mSecondaryProviderHandler.onFillRequest(mAssistReceiver.mLastFillRequest,
-                            flags);
-                }
                 mLatencyBaseTime = SystemClock.elapsedRealtime();
                 boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted;
                 mPreviouslyFillDialogPotentiallyStarted = false;
@@ -4225,6 +4240,19 @@
                     viewState.setCurrentValue(value);
                 }
 
+                if (shouldRequestSecondaryProvider(flags)) {
+                    if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+                            id, viewState, flags)) {
+                        Slog.v(TAG, "Started a new fill request for secondary provider.");
+                        return;
+                    }
+                    // If the ViewState is ready to be displayed, onReady() will be called.
+                    viewState.update(value, virtualBounds, flags);
+
+                    // return here because primary provider logic is not applicable.
+                    return;
+                }
+
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
                     if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
                     return;
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index b0bb9ec..fec5aa5 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -162,6 +162,11 @@
         return mPrimaryFillResponse;
     }
 
+    @Nullable
+    FillResponse getSecondaryResponse() {
+        return mSecondaryFillResponse;
+    }
+
     void setResponse(FillResponse response) {
         setResponse(response, /* isPrimary= */ true);
     }
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index acb5911..d08a97e 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,7 +19,13 @@
     defaults: ["platform_service_defaults"],
     srcs: [":services.backup-sources"],
     libs: ["services.core"],
-    static_libs: ["app-compat-annotations", "backup_flags_lib"],
+    static_libs: [
+        "app-compat-annotations",
+        "backup_flags_lib",
+    ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 aconfig_declarations {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 550e17b..2bfdd0a 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -31,4 +31,7 @@
         "virtualdevice_flags_lib",
         "virtual_camera_service_aidl-java",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index db40fc4..6c77018 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.os.Binder.getCallingUid;
 
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.CompanionDeviceManagerService.TAG;
@@ -41,6 +42,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
 import android.os.Binder;
+import android.os.Process;
 import android.util.Log;
 import android.util.Slog;
 
@@ -80,6 +82,11 @@
 
     static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
+        // Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP
+        if (getCallingUid() == Process.SYSTEM_UID) {
+            return;
+        }
+
         String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
 
         FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5111b08..dd001ec 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -212,6 +212,9 @@
         "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
         "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_genrule {
@@ -230,6 +233,9 @@
 java_library {
     name: "services.core",
     static_libs: ["services.core.priorityboosted"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library_host {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efb..08093c0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@
      */
     @NonNull
     public abstract PackageArchiver getPackageArchiver();
+
+    /**
+     * Returns true if the device is upgrading from an SDK version lower than the one specified.
+     */
+    public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
 }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c258370..e289a56 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
 per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3ae55271..96b1650 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface FgsStopReason {}
 
+    /**
+     * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+     * except:
+     * <ul>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+     * </ul>
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
     final ActivityManagerService mAm;
 
     // Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@
         }
     }
 
+    private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+                r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+                                                      : REASON_DENIED;
+        if (Flags.fgsBootCompleted()
+                && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+                && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+            // Filter through types
+            return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+        }
+        // Not BOOT_COMPLETED
+        return true;
+    }
+
     private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
             int callingUid, int callingPid, String callingProcessName,
             int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@
                 // anyway, so we just remove the SHORT_SERVICE type.
                 foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
             }
+            if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+                throw new ForegroundServiceStartNotAllowedException("FGS type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+                        + " not allowed to start from BOOT_COMPLETED!");
+            }
 
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
@@ -8303,7 +8339,7 @@
                 r.mFgsNotificationShown,
                 durationMs,
                 r.mStartForegroundCount,
-                ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+                0, // Short instance name -- no longer logging it.
                 r.mFgsHasNotificationPermission,
                 r.foregroundServiceType,
                 fgsTypeCheckCode,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3ce91c8..72e62c3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
 
 package com.android.server.am;
 
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
 
@@ -73,6 +79,9 @@
             = "fgservice_screen_on_before_time";
     private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
             = "fgservice_screen_on_after_time";
+
+    private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
     private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
     private static final String KEY_GC_TIMEOUT = "gc_timeout";
     private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@
     private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+    private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+            FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+                | FOREGROUND_SERVICE_TYPE_HEALTH
+                | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+                | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+                | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+                | FOREGROUND_SERVICE_TYPE_LOCATION;
+
     private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
     private static final long DEFAULT_GC_TIMEOUT = 5*1000;
     private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -225,7 +243,7 @@
     /**
      * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
      */
-    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
@@ -446,6 +464,9 @@
     // on until we will stop reporting it.
     public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
 
+    // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+    public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
     // How long we will retain processes hosting content providers in the "last activity"
     // state before allowing them to drop down to the regular cached LRU list.  This is
     // to avoid thrashing of provider processes under low memory situations.
@@ -629,6 +650,10 @@
     // foreground service background start restriction.
     volatile boolean mFgsStartRestrictionNotificationEnabled = false;
 
+    // Indicates whether PSS profiling in AppProfiler is force-enabled, even if RSS is used by
+    // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
+    volatile boolean mForceEnablePssProfiling = false;
+
     /**
      * Indicates whether the foreground service background start restriction is enabled for
      * caller app that is targeting S+.
@@ -958,6 +983,9 @@
     private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI =
             Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS);
 
+    private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
+            Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
+
     /**
      * The threshold to decide if a given association should be dumped into metrics.
      */
@@ -1368,6 +1396,7 @@
             mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI,
                     false, this);
         }
+        mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
         updateConstants();
         if (mSystemServerAutomaticHeapDumpEnabled) {
             updateEnableAutomaticSystemServerHeapDumps();
@@ -1383,6 +1412,7 @@
         // The following read from Settings.
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
+        updateForceEnablePssProfiling();
         // Read DropboxRateLimiter params from flags.
         mService.initDropboxRateLimiter();
     }
@@ -1424,6 +1454,8 @@
             updateForegroundServiceStartsLoggingEnabled();
         } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) {
             updateEnableAutomaticSystemServerHeapDumps();
+        } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
+            updateForceEnablePssProfiling();
         }
     }
 
@@ -1450,6 +1482,8 @@
                     DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
             FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
                     DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+            FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+                    DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
             CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
                     DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
             GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -1536,6 +1570,11 @@
                 Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 1) == 1;
     }
 
+    private void updateForceEnablePssProfiling() {
+        mForceEnablePssProfiling = Settings.Global.getInt(mResolver,
+                Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
+    }
+
     private void updateBackgroundActivityStarts() {
         mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2091,6 +2130,8 @@
         pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
         pw.print("  "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
         pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+        pw.print("  "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+        pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
         pw.print("  "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
         pw.println(CONTENT_PROVIDER_RETAIN_TIME);
         pw.print("  "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a80d2fd..671c8e9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -164,6 +164,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.MemoryStatUtil.hasMemcg;
 import static com.android.server.am.ProcessList.ProcStartHandler;
+import static com.android.server.flags.Flags.disableSystemCompaction;
 import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -326,7 +327,6 @@
 import android.os.DropBoxManager;
 import android.os.FactoryTest;
 import android.os.FileUtils;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdentifiersPolicyService;
@@ -8564,8 +8564,10 @@
             final long now = SystemClock.uptimeMillis();
             final long timeSinceLastIdle = now - mLastIdleTime;
 
-            // Compact all non-zygote processes to freshen up the page cache.
-            mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+            if (!disableSystemCompaction()) {
+                // Compact all non-zygote processes to freshen up the page cache.
+                mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+            }
 
             final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
             mLastIdleTime = now;
@@ -8605,7 +8607,7 @@
                         final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss;
                         synchronized (mAppProfiler.mProfilerLock) {
                             initialIdlePssOrRss = pr.getInitialIdlePssOrRss();
-                            lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+                            lastPssOrRss = mAppProfiler.isProfilingPss()
                                     ? pr.getLastPss() : pr.getLastRss();
                             lastSwapPss = pr.getLastSwapPss();
                         }
@@ -8615,14 +8617,14 @@
                             final StringBuilder sb2 = new StringBuilder(128);
                             sb2.append("Kill");
                             sb2.append(proc.processName);
-                            if (!Flags.removeAppProfilerPssCollection()) {
+                            if (mAppProfiler.isProfilingPss()) {
                                 sb2.append(" in idle maint: pss=");
                             } else {
                                 sb2.append(" in idle maint: rss=");
                             }
                             sb2.append(lastPssOrRss);
 
-                            if (!Flags.removeAppProfilerPssCollection()) {
+                            if (mAppProfiler.isProfilingPss()) {
                                 sb2.append(", swapPss=");
                                 sb2.append(lastSwapPss);
                                 sb2.append(", initialPss=");
@@ -8637,7 +8639,7 @@
                             Slog.wtfQuiet(TAG, sb2.toString());
                             mHandler.post(() -> {
                                 synchronized (ActivityManagerService.this) {
-                                    proc.killLocked(!Flags.removeAppProfilerPssCollection()
+                                    proc.killLocked(mAppProfiler.isProfilingPss()
                                             ? "idle maint (pss " : "idle maint (rss " + lastPssOrRss
                                             + " from " + initialIdlePssOrRss + ")",
                                             ApplicationExitInfo.REASON_OTHER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169..848a2b0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@
 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@
                 } else if (opt.equals("--dismiss-keyguard-if-insecure")
                       || opt.equals("--dismiss-keyguard")) {
                     mDismissKeyguardIfInsecure = true;
+                } else if (opt.equals("--allow-fgs-start-reason")) {
+                    final int reasonCode = Integer.parseInt(getNextArgRequired());
+                    mBroadcastOptions = BroadcastOptions.makeBasic();
+                    mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+                            TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                            reasonCode,
+                            "");
                 } else {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b8..78a2ecb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -129,14 +129,6 @@
     }
 
     /**
-     * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
-     * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
-     */
-    public static int hashComponentNameForAtom(String shortInstanceName) {
-        return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
-    }
-
-    /**
      * Helper method to log an unsafe intent event.
      */
     public static void logUnsafeIntentEvent(int event, int callingUid,
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 2e0aec9..e4956b3 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -602,7 +602,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case COLLECT_PSS_BG_MSG:
-                    if (!Flags.removeAppProfilerPssCollection()) {
+                    if (isProfilingPss()) {
                         collectPssInBackground();
                     } else {
                         collectRssInBackground();
@@ -748,6 +748,11 @@
         } while (true);
     }
 
+    boolean isProfilingPss() {
+        return !Flags.removeAppProfilerPssCollection()
+                || mService.mConstants.mForceEnablePssProfiling;
+    }
+
     // This method is analogous to collectPssInBackground() and is intended to be used as a
     // replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in
     // methods outside of AppProfiler have generally been kept where a new RSS equivalent is not
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 05303f6f..b68572f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -519,7 +519,7 @@
                 r.mFgsNotificationShown,
                 0, // durationMs
                 r.mStartForegroundCount,
-                ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+                0, // Short instance name -- no longer logging it.
                 r.mFgsHasNotificationPermission,
                 r.foregroundServiceType,
                 0,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b507a60..f49e25a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -143,7 +143,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
 import android.net.NetworkPolicyManager;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
@@ -2418,7 +2417,7 @@
                     // normally be a B service, but if we are low on RAM and it
                     // is large we want to force it down since we would prefer to
                     // keep launcher over it.
-                    long lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+                    long lastPssOrRss = mService.mAppProfiler.isProfilingPss()
                             ? app.mProfile.getLastPss() : app.mProfile.getLastRss();
 
                     // RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how
@@ -2427,9 +2426,8 @@
                     //
                     // TODO(b/296454553): Tune the second value so that the relative number of
                     // service B is similar before/after this flag is enabled.
-                    double thresholdModifier = !Flags.removeAppProfilerPssCollection()
-                            ? 1
-                            : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
+                    double thresholdModifier = mService.mAppProfiler.isProfilingPss()
+                            ? 1 : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
                     double cachedRestoreThreshold =
                             mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e57206e..b03183c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -94,7 +94,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.DropBoxManager;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -2482,7 +2481,6 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         app.getDisabledCompatChanges(),
-                        bindOverrideSysprops,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else if (hostingRecord.usesAppZygote()) {
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -4733,7 +4731,7 @@
                 pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
                 pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
                 // These values won't be collected if the flag is enabled.
-                if (!Flags.removeAppProfilerPssCollection()) {
+                if (service.mAppProfiler.isProfilingPss()) {
                     pw.print(" lastPss=");
                     DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
                     pw.print(" lastSwapPss=");
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 8ca64f8..d8f797c 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,7 +23,6 @@
 import android.app.ProcessMemoryState.HostingComponentType;
 import android.content.pm.ApplicationInfo;
 import android.os.Debug;
-import android.os.Flags;
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.DebugUtils;
@@ -677,7 +676,7 @@
     void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
         synchronized (mProfilerLock) {
             // TODO(b/297542292): Remove this case once PSS profiling is replaced
-            if (!Flags.removeAppProfilerPssCollection()) {
+            if (mService.mAppProfiler.isProfilingPss()) {
                 pw.print(prefix);
                 pw.print("lastPssTime=");
                 TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 5ad921f..3391ec7 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -29,7 +29,6 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.app.ActivityManager;
 import android.content.ComponentName;
-import android.os.Flags;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Slog;
@@ -1351,7 +1350,7 @@
         }
         if (mNotCachedSinceIdle) {
             pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
-            if (!Flags.removeAppProfilerPssCollection()) {
+            if (mService.mAppProfiler.isProfilingPss()) {
                 pw.print(" initialIdlePss=");
             } else {
                 pw.print(" initialIdleRss=");
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3e1edf2..d0d647c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -141,6 +141,7 @@
         "context_hub",
         "core_experiments_team_internal",
         "core_graphics",
+        "core_libraries",
         "dck_framework",
         "devoptions_settings",
         "game",
@@ -178,6 +179,7 @@
         "text",
         "threadnetwork",
         "tv_system_ui",
+        "usb",
         "vibrator",
         "virtual_devices",
         "wallet_integration",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index badd7f0..2b81dbc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2059,9 +2059,6 @@
             mTargetUserId = targetUserId;
             userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
-        if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
-            mInjector.setHasTopUi(true);
-        }
         if (userSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
@@ -2130,9 +2127,6 @@
     }
 
     private void endUserSwitch() {
-        if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
-            mInjector.setHasTopUi(false);
-        }
         final int nextUserId;
         synchronized (mLock) {
             nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3816,15 +3810,6 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
         }
 
-        void setHasTopUi(boolean hasTopUi) {
-            try {
-                Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi);
-                mService.setHasTopUi(hasTopUi);
-            } catch (RemoteException e) {
-                Slogf.e(TAG, "Failed to allow using all CPU cores", e);
-            }
-        }
-
         void onSystemUserVisibilityChanged(boolean visible) {
             getUserManagerInternal().onSystemUserVisibilityChanged(visible);
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ea791b7..37fe389 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -44,6 +44,7 @@
 import static com.android.media.audio.Flags.alarmMinVolumeZero;
 import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
 import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
 import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
 import static com.android.server.utils.EventLogger.Event.ALOGE;
 import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -607,6 +608,7 @@
     };
 
     private final boolean mUseFixedVolume;
+    private final boolean mRingerModeAffectsAlarm;
     private final boolean mUseVolumeGroupAliases;
 
     // If absolute volume is supported in AVRCP device
@@ -1300,6 +1302,9 @@
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
+        mRingerModeAffectsAlarm = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_audio_ringer_mode_affects_alarm_stream);
+
         mRecordMonitor = new RecordingActivityMonitor(mContext);
         mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
 
@@ -7019,6 +7024,19 @@
             ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
         }
 
+        if (ringerModeAffectsAlarm()) {
+            if (mRingerModeAffectsAlarm) {
+                boolean muteAlarmWithRinger =
+                        mSettings.getGlobalInt(mContentResolver,
+                        Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
+                        /* def= */ 0) != 0;
+                if (muteAlarmWithRinger) {
+                    ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_ALARM);
+                } else {
+                    ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_ALARM);
+                }
+            }
+        }
         if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
             mSettings.putSystemIntForUser(mContentResolver,
                     Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -9678,6 +9696,8 @@
                     Settings.Global.ZEN_MODE), false, this);
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this);
+            mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE), false, this);
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 2cceb5a..cbcd8f5 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -346,6 +346,7 @@
             if (apc.getPlayerProxy() != null) {
                 applyVolumeShaperInternal(apc, piid, volShaper,
                         skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+                mFadedPlayers.put(piid, volShaper);
             } else {
                 if (DEBUG) {
                     Slog.v(TAG, "Error fading out player piid:" + piid
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6af223b..0f964bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -78,6 +78,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -99,6 +100,9 @@
             mBiometricStateCallback;
     @NonNull
     private final FaceProviderFunction mFaceProviderFunction;
+    @NonNull private final Function<String, FaceProvider> mFaceProvider;
+    @NonNull
+    private final Supplier<String[]> mAidlInstanceNameSupplier;
 
     interface FaceProviderFunction {
         FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
@@ -671,23 +675,9 @@
             final List<ServiceProvider> providers = new ArrayList<>();
 
             for (String instance : instances) {
-                final String fqName = IFace.DESCRIPTOR + "/" + instance;
-                final IFace face = IFace.Stub.asInterface(
-                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
-                if (face == null) {
-                    Slog.e(TAG, "Unable to get declared service: " + fqName);
-                    continue;
-                }
-                try {
-                    final SensorProps[] props = face.getSensorProps();
-                    final FaceProvider provider = new FaceProvider(getContext(),
-                            mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
-                            BiometricContext.getInstance(getContext()),
-                            false /* resetLockoutRequiresChallenge */);
-                    providers.add(provider);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
-                }
+                final FaceProvider provider = mFaceProvider.apply(instance);
+                Slog.i(TAG, "Adding AIDL provider: " + instance);
+                providers.add(provider);
             }
 
             return providers;
@@ -700,7 +690,7 @@
 
             mRegistry.registerAll(() -> {
                 List<String> aidlSensors = new ArrayList<>();
-                final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+                final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
@@ -813,11 +803,15 @@
 
     public FaceService(Context context) {
         this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
-                ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
+                () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
     }
 
-    @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
-            Supplier<IBiometricService> biometricServiceSupplier) {
+    @VisibleForTesting FaceService(Context context,
+            FaceProviderFunction faceProviderFunction,
+            Supplier<IBiometricService> biometricServiceSupplier,
+            Function<String, FaceProvider> faceProvider,
+            Supplier<String[]> aidlInstanceNameSupplier) {
         super(context);
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
@@ -830,6 +824,28 @@
                 mBiometricStateCallback.start(mRegistry.getProviders());
             }
         });
+        mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+
+        mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
+            final String fqName = IFace.DESCRIPTOR + "/" + name;
+            final IFace face = IFace.Stub.asInterface(
+                    Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+            if (face == null) {
+                Slog.e(TAG, "Unable to get declared service: " + fqName);
+                return null;
+            }
+            try {
+                final SensorProps[] props = face.getSensorProps();
+                return new FaceProvider(getContext(),
+                        mBiometricStateCallback, props, name, mLockoutResetDispatcher,
+                        BiometricContext.getInstance(getContext()),
+                        false /* resetLockoutRequiresChallenge */);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+            }
+
+            return null;
+        };
 
         if (Flags.deHidl()) {
             mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd2..8910b6e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@
     public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
     public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
     public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+    public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
 
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 6a4b00f..8405e0a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -23,12 +23,14 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessCorrection;
 import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.LongArray;
 import android.util.MathUtils;
 import android.util.Pair;
@@ -80,45 +82,50 @@
      * Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
      * mode.
      *
-     * @param resources
+     * @param context
      * @param displayDeviceConfig
      * @param mode The auto-brightness mode. Different modes use different brightness curves
      * @param displayWhiteBalanceController
      * @return the BrightnessMappingStrategy
      */
     @Nullable
-    static BrightnessMappingStrategy create(Resources resources,
+    static BrightnessMappingStrategy create(Context context,
             DisplayDeviceConfig displayDeviceConfig,
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
-            DisplayWhiteBalanceController displayWhiteBalanceController) {
+            @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
 
         // Display independent, mode dependent values
         float[] brightnessLevelsNits = null;
         float[] brightnessLevels = null;
         float[] luxLevels = null;
+        int preset = Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
         switch (mode) {
             case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
                 brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
-                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode);
-                brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode);
+                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+                brightnessLevels =
+                        displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
             }
             case AUTO_BRIGHTNESS_MODE_IDLE -> {
-                brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+                brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray(
                         com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
-                luxLevels = getLuxLevels(resources.getIntArray(
+                luxLevels = getLuxLevels(context.getResources().getIntArray(
                         com.android.internal.R.array.config_autoBrightnessLevelsIdle));
             }
             case AUTO_BRIGHTNESS_MODE_DOZE -> {
-                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode);
-                brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode);
+                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+                brightnessLevels =
+                        displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
             }
         }
 
         // Display independent, mode independent values
-        float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
+        float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction(
                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
                 1, 1);
-        long shortTermModelTimeout = resources.getInteger(
+        long shortTermModelTimeout = context.getResources().getInteger(
                 com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
 
         // Display dependent values - used for physical mapping strategy nits -> brightness
@@ -823,6 +830,8 @@
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
         private float mUserBrightness;
+
+        @Nullable
         private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
 
         @AutomaticBrightnessController.AutomaticBrightnessMode
@@ -838,7 +847,7 @@
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
                 float[] brightness, float maxGamma,
                 @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
+                @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
                     "Nits and brightness arrays must not be empty!");
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a6f42d7..bd22e1d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1592,24 +1592,13 @@
 
     /**
      * @param mode The auto-brightness mode
-     * @return The default auto-brightness brightening ambient lux levels for the specified mode
-     * and the normal brightness preset
-     */
-    public float[] getAutoBrightnessBrighteningLevelsLux(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        if (mDisplayBrightnessMapping == null) {
-            return null;
-        }
-        return mDisplayBrightnessMapping.getLuxArray(mode);
-    }
-
-    /**
-     * @param mode The auto-brightness mode
      * @param preset The brightness preset. Presets are used on devices that allow users to choose
      *               from a set of predefined options in display auto-brightness settings.
-     * @return Auto brightness brightening ambient lux levels for the specified mode and preset
+     * @return The default auto-brightness brightening ambient lux levels for the specified mode
+     * and preset
      */
-    public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String preset) {
+    public float[] getAutoBrightnessBrighteningLevelsLux(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
         if (mDisplayBrightnessMapping == null) {
             return null;
         }
@@ -1628,24 +1617,12 @@
 
     /**
      * @param mode The auto-brightness mode
-     * @return The default auto-brightness brightening levels for the specified mode and the normal
-     * brightness preset
-     */
-    public float[] getAutoBrightnessBrighteningLevels(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        if (mDisplayBrightnessMapping == null) {
-            return null;
-        }
-        return mDisplayBrightnessMapping.getBrightnessArray(mode);
-    }
-
-    /**
-     * @param mode The auto-brightness mode
      * @param preset The brightness preset. Presets are used on devices that allow users to choose
      *               from a set of predefined options in display auto-brightness settings.
-     * @return Auto brightness brightening backlight levels for the specified mode and preset
+     * @return The default auto-brightness brightening levels for the specified mode and preset
      */
-    public float[] getAutoBrightnessBrighteningLevels(String mode, String preset) {
+    public float[] getAutoBrightnessBrighteningLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
         if (mDisplayBrightnessMapping == null) {
             return null;
         }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08f..bc3f9dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@
                     if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
                         final DisplayPowerControllerInterface displayPowerController =
                                 mDisplayPowerControllers.get(id);
-                        ready &= displayPowerController.requestPowerState(request,
-                                waitForNegativeProximity);
+                        if (displayPowerController != null) {
+                            ready &= displayPowerController.requestPowerState(request,
+                                    waitForNegativeProximity);
+                        }
                     }
                 }
 
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..a43f93a 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
@@ -65,6 +67,22 @@
         return true;
     }
 
+    @Override
+    public float[] getAutoBrightnessLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLevels(mode);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+    }
+
     /**
      * Start the offload session. The method returns if the session is already active.
      * @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2685efe..734381b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -735,7 +735,7 @@
             mCdsi = null;
         }
 
-        setUpAutoBrightness(resources, handler);
+        setUpAutoBrightness(context, handler);
 
         mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
                 && !resources.getBoolean(
@@ -1075,7 +1075,7 @@
         loadBrightnessRampRates();
         loadProximitySensor();
         loadNitsRange(mContext.getResources());
-        setUpAutoBrightness(mContext.getResources(), mHandler);
+        setUpAutoBrightness(mContext, mHandler);
         reloadReduceBrightColours();
         setAnimatorRampSpeeds(/* isIdleMode= */ false);
         mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
@@ -1145,7 +1145,7 @@
         handleBrightnessModeChange();
     }
 
-    private void setUpAutoBrightness(Resources resources, Handler handler) {
+    private void setUpAutoBrightness(Context context, Handler handler) {
         mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
 
         if (!mUseSoftwareAutoBrightnessConfig) {
@@ -1155,21 +1155,19 @@
         SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
 
         BrightnessMappingStrategy defaultModeBrightnessMapper =
-                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
                         mDisplayWhiteBalanceController);
         brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 defaultModeBrightnessMapper);
 
-        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+        final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
         if (isIdleScreenBrightnessEnabled) {
             BrightnessMappingStrategy idleModeBrightnessMapper =
-                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
-                            AUTO_BRIGHTNESS_MODE_IDLE,
-                            mDisplayWhiteBalanceController);
+                    BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+                            AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
             if (idleModeBrightnessMapper != null) {
-                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
-                        idleModeBrightnessMapper);
+                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
             }
         }
 
@@ -1181,7 +1179,7 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = resources.getFraction(
+            final float dozeScaleFactor = context.getResources().getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
@@ -1265,14 +1263,14 @@
                     .getAutoBrightnessBrighteningLightDebounceIdle();
             long darkeningLightDebounceIdle = mDisplayDeviceConfig
                     .getAutoBrightnessDarkeningLightDebounceIdle();
-            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
                     com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
-            int lightSensorWarmUpTimeConfig = resources.getInteger(
+            int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
                     com.android.internal.R.integer.config_lightSensorWarmupTime);
-            int lightSensorRate = resources.getInteger(
+            int lightSensorRate = context.getResources().getInteger(
                     com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
-            int initialLightSensorRate = resources.getInteger(
+            int initialLightSensorRate = context.getResources().getInteger(
                     com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
             if (initialLightSensorRate == -1) {
                 initialLightSensorRate = lightSensorRate;
@@ -2216,6 +2214,20 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -3645,12 +3657,11 @@
                     userNits);
         }
 
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                    displayWhiteBalanceController);
+            return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+                    AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index c088a6d..7df6114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -19,6 +19,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -624,7 +625,7 @@
             mCdsi = null;
         }
 
-        setUpAutoBrightness(resources, handler);
+        setUpAutoBrightness(context, handler);
 
         mColorFadeEnabled = mInjector.isColorFadeEnabled()
                 && !resources.getBoolean(
@@ -905,7 +906,7 @@
         // updated here.
         loadBrightnessRampRates();
         loadNitsRange(mContext.getResources());
-        setUpAutoBrightness(mContext.getResources(), mHandler);
+        setUpAutoBrightness(mContext, mHandler);
         reloadReduceBrightColours();
         setAnimatorRampSpeeds(/* isIdleMode= */ false);
 
@@ -976,10 +977,15 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        if (mFlags.areAutoBrightnessModesEnabled()) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
+                    /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+        }
         handleBrightnessModeChange();
     }
 
-    private void setUpAutoBrightness(Resources resources, Handler handler) {
+    private void setUpAutoBrightness(Context context, Handler handler) {
         mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
 
         if (!mUseSoftwareAutoBrightnessConfig) {
@@ -989,16 +995,16 @@
         SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
 
         BrightnessMappingStrategy defaultModeBrightnessMapper =
-                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
                         mDisplayWhiteBalanceController);
         brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 defaultModeBrightnessMapper);
 
-        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+        final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
         if (isIdleScreenBrightnessEnabled) {
             BrightnessMappingStrategy idleModeBrightnessMapper =
-                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+                    BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
                             AUTO_BRIGHTNESS_MODE_IDLE,
                             mDisplayWhiteBalanceController);
             if (idleModeBrightnessMapper != null) {
@@ -1008,7 +1014,7 @@
         }
 
         BrightnessMappingStrategy dozeModeBrightnessMapper =
-                BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+                BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
                         AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
         if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
             brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
@@ -1022,7 +1028,7 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = resources.getFraction(
+            final float dozeScaleFactor = context.getResources().getFraction(
                     R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
@@ -1106,14 +1112,14 @@
                     .getAutoBrightnessBrighteningLightDebounceIdle();
             long darkeningLightDebounceIdle = mDisplayDeviceConfig
                     .getAutoBrightnessDarkeningLightDebounceIdle();
-            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
                     R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
-            int lightSensorWarmUpTimeConfig = resources.getInteger(
+            int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
                     R.integer.config_lightSensorWarmupTime);
-            int lightSensorRate = resources.getInteger(
+            int lightSensorRate = context.getResources().getInteger(
                     R.integer.config_autoBrightnessLightSensorRate);
-            int initialLightSensorRate = resources.getInteger(
+            int initialLightSensorRate = context.getResources().getInteger(
                     R.integer.config_autoBrightnessInitialLightSensorRate);
             if (initialLightSensorRate == -1) {
                 initialLightSensorRate = lightSensorRate;
@@ -1880,6 +1886,24 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -2999,6 +3023,16 @@
         public void onChange(boolean selfChange, Uri uri) {
             if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
                 handleBrightnessModeChange();
+            } else if (uri.equals(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
+                int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+                        UserHandle.USER_CURRENT);
+                Slog.i(mTag, "Setting up auto-brightness for preset "
+                        + autoBrightnessPresetToString(preset));
+                setUpAutoBrightness(mContext, mHandler);
+                sendUpdatePowerState();
             } else {
                 handleSettingsChange(false /* userSwitch */);
             }
@@ -3117,12 +3151,11 @@
                     userNits);
         }
 
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                    displayWhiteBalanceController);
+            return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+                    AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..13acb3f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@
      * Indicate that boot has been completed and the screen is ready to update.
      */
     void onBootCompleted();
+
+    /**
+     * Get the brightness levels used to determine automatic brightness based on lux levels.
+     * @param mode The auto-brightness mode
+     * @return The brightness levels for the specified mode. The values are between
+     * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+     */
+    float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+    /**
+     * Get the lux levels used to determine automatic brightness.
+     * @param mode The auto-brightness mode
+     * @return The lux levels for the specified mode
+     */
+    float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index f994c05..bcf27b4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,9 +26,12 @@
 import android.view.Choreographer;
 import android.view.Display;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 /**
  * Controls the display power state.
@@ -75,10 +78,19 @@
 
     private Runnable mCleanListener;
 
+    private Executor mAsyncDestroyExecutor;
+
     private volatile boolean mStopped;
 
     DisplayPowerState(
             DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
+        this(blanker, colorFade, displayId, displayState, BackgroundThread.getExecutor());
+    }
+
+    @VisibleForTesting
+    DisplayPowerState(
+            DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
+            Executor asyncDestroyExecutor) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
         mBlanker = blanker;
@@ -86,6 +98,7 @@
         mPhotonicModulator = new PhotonicModulator();
         mPhotonicModulator.start();
         mDisplayId = displayId;
+        mAsyncDestroyExecutor = asyncDestroyExecutor;
 
         // At boot time, we don't know the screen's brightness,
         // so prepare to set it to a known state when the state is next applied.
@@ -321,7 +334,7 @@
         mStopped = true;
         mPhotonicModulator.interrupt();
         if (mColorFade != null) {
-            mColorFade.destroy();
+            mAsyncDestroyExecutor.execute(mColorFade::destroy);
         }
         mCleanListener = null;
         mHandler.removeCallbacksAndMessages(null);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 200d88a..01a8d360a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -104,8 +104,7 @@
     public void resetHdrConfig(HdrBrightnessData data, int width, int height,
             float minimumHdrPercentOfScreen, IBinder displayToken) {
         mHdrBrightnessData = data;
-        mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
-                : (float) (width * height) * minimumHdrPercentOfScreen;
+        mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
         if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
             if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
                 mHdrListener.unregister(mRegisteredDisplayToken);
@@ -115,7 +114,7 @@
             // new token not null and hdr min % of the screen is set, subscribe.
             // e.g. for virtual display, HBM data will be missing and HdrListener
             // should not be registered
-            if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
+            if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
                 mHdrListener.register(displayToken);
                 mRegisteredDisplayToken = displayToken;
             }
@@ -140,8 +139,11 @@
         pw.println("  mDesiredMaxBrightness=" + mDesiredMaxBrightness);
         pw.println("  mTransitionRate=" + mTransitionRate);
         pw.println("  mDesiredTransitionRate=" + mDesiredTransitionRate);
+        pw.println("  mHdrVisible=" + mHdrVisible);
+        pw.println("  mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
         pw.println("  mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
                 : mHdrBrightnessData.toString()));
+        pw.println("  mHdrListener registered=" + (mRegisteredDisplayToken != null));
         pw.println("  mAmbientLux=" + mAmbientLux);
     }
 
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 8f12329..6978686 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.os.PowerManager;
+import android.provider.Settings;
 import android.util.Spline;
 
 import com.android.internal.display.BrightnessSynchronizer;
@@ -38,9 +39,9 @@
  */
 public class DisplayBrightnessMappingConfig {
 
-    private static final String DEFAULT_BRIGHTNESS_PRESET_NAME = "normal";
     private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY =
-            AutoBrightnessModeName._default.getRawName() + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME;
+            AutoBrightnessModeName._default.getRawName() + "_"
+                    + AutoBrightnessSettingName.normal.getRawName();
 
     /**
      * Array of desired screen brightness in nits corresponding to the lux values
@@ -152,22 +153,15 @@
 
     /**
      * @param mode The auto-brightness mode
-     * @return The default auto-brightness brightening ambient lux levels for the specified mode
-     * and the normal brightness preset
-     */
-    public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        return mBrightnessLevelsLuxMap.get(
-                autoBrightnessModeToString(mode) + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME);
-    }
-
-    /**
-     * @param mode The auto-brightness mode
      * @param preset The brightness preset. Presets are used on devices that allow users to choose
      *               from a set of predefined options in display auto-brightness settings.
-     * @return Auto brightness brightening ambient lux levels for the specified mode and preset
+     * @return The default auto-brightness brightening ambient lux levels for the specified mode
+     * and preset
      */
-    public float[] getLuxArray(String mode, String preset) {
-        return mBrightnessLevelsLuxMap.get(mode + "_" + preset);
+    public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+            int preset) {
+        return mBrightnessLevelsLuxMap.get(
+                autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
     }
 
     /**
@@ -179,23 +173,14 @@
 
     /**
      * @param mode The auto-brightness mode
-     * @return The default auto-brightness brightening levels for the specified mode and the normal
-     * brightness preset
-     */
-    public float[] getBrightnessArray(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        return mBrightnessLevelsMap.get(
-                autoBrightnessModeToString(mode) + "_" + DEFAULT_BRIGHTNESS_PRESET_NAME);
-    }
-
-    /**
-     * @param mode The auto-brightness mode
      * @param preset The brightness preset. Presets are used on devices that allow users to choose
      *               from a set of predefined options in display auto-brightness settings.
-     * @return Auto brightness brightening ambient lux levels for the specified mode and preset
+     * @return The default auto-brightness brightening levels for the specified mode and preset
      */
-    public float[] getBrightnessArray(String mode, String preset) {
-        return mBrightnessLevelsMap.get(mode + "_" + preset);
+    public float[] getBrightnessArray(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+        return mBrightnessLevelsMap.get(
+                autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
     }
 
     @Override
@@ -247,6 +232,24 @@
         }
     }
 
+    /**
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The string representing the preset
+     */
+    public static String autoBrightnessPresetToString(int preset) {
+        return switch (preset) {
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM ->
+                    AutoBrightnessSettingName.dim.getRawName();
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL ->
+                    AutoBrightnessSettingName.normal.getRawName();
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT ->
+                    AutoBrightnessSettingName.bright.getRawName();
+            default -> throw new IllegalArgumentException(
+                    "Unknown auto-brightness preset value: " + preset);
+        };
+    }
+
     private float[] brightnessArrayIntToFloat(int[] brightnessInt,
             Spline backlightToBrightnessSpline) {
         float[] brightnessFloat = new float[brightnessInt.length];
diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS
index 535a750..60ceb12 100644
--- a/services/core/java/com/android/server/flags/OWNERS
+++ b/services/core/java/com/android/server/flags/OWNERS
@@ -1 +1,2 @@
-per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file
+per-file pinner.aconfig = edgararriaga@google.com
+per-file compaction.aconfig = edgararriaga@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
new file mode 100644
index 0000000..58cc560
--- /dev/null
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+    name: "disable_system_compaction"
+    namespace: "system_performance"
+    description: "This flag controls if all processes compaction should happen during idle maintenance."
+    bug: "314328789"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 64abb81..81204ef 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1314,7 +1314,6 @@
      */
     protected void disableDevice(
             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
         removeAction(SetAudioVolumeLevelDiscoveryAction.class);
         removeAction(ActiveSourceAction.class);
         removeAction(ResendCecCommandAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 29303aa..6157402 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -308,7 +308,6 @@
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         removeAction(OneTouchPlayAction.class);
         removeAction(DevicePowerStatusAction.class);
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         super.disableDevice(initiatedByCec, callback);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5831b29..1cd267d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1336,7 +1336,6 @@
         removeAction(OneTouchRecordAction.class);
         removeAction(TimerRecordingAction.class);
         removeAction(NewDeviceAction.class);
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
         // Remove pending actions.
         removeAction(RequestActiveSourceAction.class);
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9253706..eaf754d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3793,6 +3793,11 @@
                 }
             }
         });
+
+        // Make sure we switch away from absolute volume behavior (AVB) when entering standby.
+        // We do this because AVB should not be used unless AbsoluteVolumeAudioStatusAction exists,
+        // and the action cannot exist in standby because there are no local devices.
+        checkAndUpdateAbsoluteVolumeBehavior();
     }
 
     boolean canGoToStandby() {
@@ -4446,10 +4451,11 @@
      * This allows the volume level of the System Audio device to be tracked and set by Android.
      *
      * Absolute volume behavior requires the following conditions:
-     * 1. If the System Audio Device is an Audio System: System Audio Mode is active
-     * 2. All AVB-capable audio output devices are already using full/absolute volume behavior
-     * 3. CEC volume is enabled
-     * 4. The System Audio device supports the <Set Audio Volume Level> message
+     * 1. The device is not in standby or transient to standby
+     * 2. If the System Audio Device is an Audio System: System Audio Mode is active
+     * 3. All AVB-capable audio output devices are already using full/absolute volume behavior
+     * 4. CEC volume is enabled
+     * 5. The System Audio device supports the <Set Audio Volume Level> message
      *
      * This method enables adjust-only absolute volume behavior on TV panels when conditions
      * 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
@@ -4465,10 +4471,16 @@
             return;
         }
 
+        // Condition 1: The device is not in standby or transient to standby
+        if (mPowerStatusController != null && isPowerStandbyOrTransient()) {
+            switchToFullVolumeBehavior();
+            return;
+        }
+
         HdmiCecLocalDevice localCecDevice;
         if (isTvDevice() && tv() != null) {
             localCecDevice = tv();
-            // Condition 1: TVs need System Audio Mode to be active
+            // Condition 2: TVs need System Audio Mode to be active
             // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
             // TV is the System Audio Device instead.)
             if (!isSystemAudioActivated()) {
@@ -4485,7 +4497,7 @@
         HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
                 localCecDevice.findAudioReceiverAddress());
 
-        // Condition 2: All AVB-capable audio outputs already use full/absolute volume behavior
+        // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior
         // We only need to check the first AVB-capable audio output because only TV panels
         // have more than one of them, and they always have the same volume behavior.
         @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
@@ -4493,7 +4505,7 @@
         boolean alreadyUsingFullOrAbsoluteVolume =
                 FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior);
 
-        // Condition 3: CEC volume is enabled
+        // Condition 4: CEC volume is enabled
         boolean cecVolumeEnabled =
                 getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
 
@@ -4509,7 +4521,7 @@
             return;
         }
 
-        // Condition 4: The System Audio device supports <Set Audio Volume Level>
+        // Condition 5: The System Audio device supports <Set Audio Volume Level>
         switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
             case DeviceFeatures.FEATURE_SUPPORTED:
                 if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
@@ -4556,6 +4568,8 @@
      * are currently used. Removes the action for handling volume updates for these behaviors.
      */
     private void switchToFullVolumeBehavior() {
+        Slog.d(TAG, "Switching to full volume behavior");
+
         if (playback() != null) {
             playback().removeAvbAudioStatusAction();
         } else if (tv() != null) {
@@ -4597,12 +4611,14 @@
         // Otherwise, enable adjust-only AVB on TVs only.
         if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
                 == DeviceFeatures.FEATURE_SUPPORTED) {
+            Slog.d(TAG, "Enabling absolute volume behavior");
             for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
                 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
                         device, volumeInfo, mServiceThreadExecutor,
                         mAbsoluteVolumeChangedListener, true);
             }
         } else if (tv() != null) {
+            Slog.d(TAG, "Enabling adjust-only absolute volume behavior");
             for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
                 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
                         device, volumeInfo, mServiceThreadExecutor,
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 24e23003..087c525 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2523,9 +2523,9 @@
     // Native callback.
     @SuppressWarnings("unused")
     private int interceptMotionBeforeQueueingNonInteractive(int displayId,
-            long whenNanos, int policyFlags) {
+            int source, int action, long whenNanos, int policyFlags) {
         return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive(
-                displayId, whenNanos, policyFlags);
+                displayId, source, action, whenNanos, policyFlags);
     }
 
     // Native callback.
@@ -2901,8 +2901,8 @@
          * processing when the device is in a non-interactive state since these events are normally
          * dropped.
          */
-        int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-                int policyFlags);
+        int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+                long whenNanos, int policyFlags);
 
         /**
          * This callback is invoked just before the key is about to be sent to an application.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 773293f..a88d85e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -229,31 +229,6 @@
             }
         }
 
-        private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
-                String enabledInputMethodsStr,
-                TextUtils.SimpleStringSplitter inputMethodSplitter,
-                TextUtils.SimpleStringSplitter subtypeSplitter) {
-            ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
-            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
-                return imsList;
-            }
-            inputMethodSplitter.setString(enabledInputMethodsStr);
-            while (inputMethodSplitter.hasNext()) {
-                String nextImsStr = inputMethodSplitter.next();
-                subtypeSplitter.setString(nextImsStr);
-                if (subtypeSplitter.hasNext()) {
-                    ArrayList<String> subtypeHashes = new ArrayList<>();
-                    // The first element is ime id.
-                    String imeId = subtypeSplitter.next();
-                    while (subtypeSplitter.hasNext()) {
-                        subtypeHashes.add(subtypeSplitter.next());
-                    }
-                    imsList.add(new Pair<>(imeId, subtypeHashes));
-                }
-            }
-            return imsList;
-        }
-
         InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
                 boolean copyOnWrite) {
             mMethodMap = methodMap;
@@ -351,18 +326,30 @@
         }
 
         List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
-            return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
-        }
-
-        List<String> getEnabledInputMethodNames() {
-            List<String> result = new ArrayList<>();
-            for (Pair<String, ArrayList<String>> pair :
-                    getEnabledInputMethodsAndSubtypeListLocked()) {
-                result.add(pair.first);
+            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+            final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+            final TextUtils.SimpleStringSplitter subtypeSplitter =
+                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+            final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+                return imsList;
             }
-            return result;
+            inputMethodSplitter.setString(enabledInputMethodsStr);
+            while (inputMethodSplitter.hasNext()) {
+                String nextImsStr = inputMethodSplitter.next();
+                subtypeSplitter.setString(nextImsStr);
+                if (subtypeSplitter.hasNext()) {
+                    ArrayList<String> subtypeHashes = new ArrayList<>();
+                    // The first element is ime id.
+                    String imeId = subtypeSplitter.next();
+                    while (subtypeSplitter.hasNext()) {
+                        subtypeHashes.add(subtypeSplitter.next());
+                    }
+                    imsList.add(new Pair<>(imeId, subtypeHashes));
+                }
+            }
+            return imsList;
         }
 
         void appendAndPutEnabledInputMethodLocked(String id) {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index f2dcba4..5514ec7 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -67,7 +68,7 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.RuleMetadata;
 import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -141,7 +142,7 @@
         return new AppIntegrityManagerServiceImpl(
                 context,
                 LocalServices.getService(PackageManagerInternal.class),
-                PackageParser2::forParsingFileWithDefaults,
+                PackageParserUtils::forParsingFileWithDefaults,
                 RuleEvaluationEngine.getRuleEvaluationEngine(),
                 IntegrityFileManager.getInstance(),
                 handlerThread.getThreadHandler());
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5..9c4225d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.flags.Flags;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@
 
         location.setExtras(mLocationExtras.getBundle());
 
-        reportLocation(LocationResult.wrap(location).validate());
+        try {
+            reportLocation(LocationResult.wrap(location).validate());
+        } catch (BadLocationException e) {
+            throw new IllegalArgumentException(e);
+        }
 
         if (mStarted) {
             mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@
                 }
             }
 
-            reportLocation(LocationResult.wrap(locations).validate());
+            try {
+                reportLocation(LocationResult.wrap(locations).validate());
+            } catch (BadLocationException e) {
+                throw new IllegalArgumentException(e);
+            }
         }
 
         Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80..7d44aec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.altitude.AltitudeConverter;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@
                                         < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
                                     if (D) {
                                         Log.v(TAG, mName + " provider registration " + getIdentity()
-                                                + " dropped delivery - too fast");
+                                                + " dropped delivery - too fast (deltaMs="
+                                                + deltaMs + ").");
                                     }
                                     return false;
                                 }
@@ -2574,29 +2576,17 @@
     @GuardedBy("mMultiplexerLock")
     @Nullable
     private LocationResult processReportedLocation(LocationResult locationResult) {
-        LocationResult processed = locationResult.filter(location -> {
-            if (!location.isMock()) {
-                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
-                    Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
-                    return false;
-                }
-            }
-
-            if (!location.isComplete()) {
-                Log.e(TAG, "blocking incomplete location from " + mName + " provider");
-                return false;
-            }
-
-            return true;
-        });
-        if (processed == null) {
+        try {
+            locationResult.validate();
+        } catch (BadLocationException e) {
+            Log.e(TAG, "Dropping invalid locations: " + e);
             return null;
         }
 
         // Attempt to add a missing MSL altitude on behalf of the provider.
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
                 "enable_location_provider_manager_msl", true)) {
-            return processed.map(location -> {
+            return locationResult.map(location -> {
                 if (!location.hasMslAltitude() && location.hasAltitude()) {
                     try {
                         Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@
                 return location;
             });
         }
-        return processed;
+        return locationResult;
     }
 
     @GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4..4efacd7 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@
         Location location = new Location(l);
         location.setIsFromMockProvider(true);
         mLocation = location;
-        reportLocation(LocationResult.wrap(location).validate());
+        try {
+            reportLocation(LocationResult.wrap(location).validate());
+        } catch (BadLocationException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da..a597edd 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@
                     return;
                 }
 
-                reportLocation(LocationResult.wrap(location).validate());
+                reportLocation(LocationResult.wrap(location));
             }
         }
 
@@ -316,8 +316,7 @@
                 if (mProxy != this) {
                     return;
                 }
-
-                reportLocation(LocationResult.wrap(locations).validate());
+                reportLocation(LocationResult.wrap(locations));
             }
         }
 
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 8504495..0eb9166 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -87,6 +87,8 @@
 
     @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
 
+    @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
     @NonNull
     private final AudioManager.OnDevicesForAttributesChangedListener
             mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
@@ -113,6 +115,10 @@
         mHandler = new Handler(Objects.requireNonNull(looper));
         mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
         mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+
+        mBuiltInSpeakerSuitabilityStatus =
+                DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
         mBluetoothRouteController =
                 new AudioPoliciesBluetoothRouteController(
                         mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
@@ -373,14 +379,19 @@
             // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
             routeId = systemRouteInfo.mDefaultRouteId;
         }
-        return new MediaRoute2Info.Builder(routeId, humanReadableName)
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName)
                 .setType(systemRouteInfo.mMediaRoute2InfoType)
                 .setAddress(address)
                 .setSystemRoute(true)
                 .addFeature(FEATURE_LIVE_AUDIO)
                 .addFeature(FEATURE_LOCAL_PLAYBACK)
-                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                .build();
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+        if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) {
+            builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+        }
+
+        return builder.build();
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 9f175a9..8b62cc9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -81,6 +81,30 @@
         }
     }
 
+    /** Returns device route availability status. */
+    @MediaRoute2Info.SuitabilityStatus
+    static int getBuiltInSpeakerSuitabilityStatus(@NonNull Context context) {
+        if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            // Route is always suitable if the flag is disabled.
+            return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+        }
+
+        int availabilityStatus =
+                context.getResources()
+                        .getInteger(
+                                com.android.internal.R.integer
+                                        .config_mediaRouter_builtInSpeakerSuitability);
+
+        switch (availabilityStatus) {
+            case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER:
+            case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER:
+            case MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER:
+                return availabilityStatus;
+            default:
+                return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+        }
+    }
+
     /** Returns the currently selected device (built-in or wired) route. */
     @NonNull
     MediaRoute2Info getSelectedRoute();
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index c0f2834..65b0ad0 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -72,6 +72,8 @@
     @NonNull
     private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
 
+    @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
     private int mDeviceVolume;
     private MediaRoute2Info mDeviceRoute;
 
@@ -90,6 +92,9 @@
         mAudioManager = audioManager;
         mAudioService = audioService;
 
+        mBuiltInSpeakerSuitabilityStatus =
+                DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
         AudioRoutesInfo newAudioRoutes = null;
         try {
             newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
@@ -165,19 +170,28 @@
         }
 
         synchronized (this) {
-            return new MediaRoute2Info.Builder(
-                    DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
-                    .setVolumeHandling(mAudioManager.isVolumeFixed()
-                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
-                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                    .setVolume(mDeviceVolume)
-                    .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                    .setType(type)
-                    .addFeature(FEATURE_LIVE_AUDIO)
-                    .addFeature(FEATURE_LIVE_VIDEO)
-                    .addFeature(FEATURE_LOCAL_PLAYBACK)
-                    .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                    .build();
+            MediaRoute2Info.Builder builder =
+                    new MediaRoute2Info.Builder(
+                                    DEVICE_ROUTE_ID,
+                                    mContext.getResources().getText(name).toString())
+                            .setVolumeHandling(
+                                    mAudioManager.isVolumeFixed()
+                                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+                                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                            .setVolume(mDeviceVolume)
+                            .setVolumeMax(
+                                    mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                            .setType(type)
+                            .addFeature(FEATURE_LIVE_AUDIO)
+                            .addFeature(FEATURE_LIVE_VIDEO)
+                            .addFeature(FEATURE_LOCAL_PLAYBACK)
+                            .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+            if (type == TYPE_BUILTIN_SPEAKER) {
+                builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+            }
+
+            return builder.build();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 8149847..1bc2a5e 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -24,6 +24,7 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -54,8 +55,15 @@
         mCallback = callback;
     }
 
-    public abstract void requestCreateSession(long requestId, String packageName, String routeId,
-            @Nullable Bundle sessionHints);
+    public abstract void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            @Nullable Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName);
+
     public abstract void releaseSession(long requestId, String sessionId);
 
     public abstract void updateDiscoveryPreference(
@@ -63,7 +71,14 @@
 
     public abstract void selectRoute(long requestId, String sessionId, String routeId);
     public abstract void deselectRoute(long requestId, String sessionId, String routeId);
-    public abstract void transferToRoute(long requestId, String sessionId, String routeId);
+
+    public abstract void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason);
 
     public abstract void setRouteVolume(long requestId, String routeId, int volume);
     public abstract void setSessionVolume(long requestId, String sessionId, int volume);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 330818e..ae889d8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -98,8 +98,14 @@
     }
 
     @Override
-    public void requestCreateSession(long requestId, String packageName, String routeId,
-            Bundle sessionHints) {
+    public void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         if (mConnectionReady) {
             mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
             updateBinding();
@@ -141,7 +147,13 @@
     }
 
     @Override
-    public void transferToRoute(long requestId, String sessionId, String routeId) {
+    public void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason) {
         if (mConnectionReady) {
             mActiveConnection.transferToRoute(requestId, sessionId, routeId);
         }
@@ -649,6 +661,14 @@
                                     + "Disallowed route: "
                                     + route);
                 }
+
+                if (route.getSuitabilityStatus()
+                        == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) {
+                    throw new SecurityException(
+                            "Only the system is allowed to set not suitable for transfer status. "
+                                    + "Disallowed route: "
+                                    + route);
+                }
             }
 
             Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e18727..9088cb9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -337,18 +337,47 @@
         }
     }
 
-    public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId,
-            long managerRequestId, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route, Bundle sessionHints) {
+    public void requestCreateSessionWithRouter2(
+            @NonNull IMediaRouter2 router,
+            int requestId,
+            long managerRequestId,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            Bundle sessionHints,
+            @Nullable UserHandle transferInitiatorUserHandle,
+            @Nullable String transferInitiatorPackageName) {
         Objects.requireNonNull(router, "router must not be null");
         Objects.requireNonNull(oldSession, "oldSession must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
+        synchronized (mLock) {
+            if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE
+                    || transferInitiatorUserHandle == null
+                    || transferInitiatorPackageName == null) {
+                final IBinder binder = router.asBinder();
+                final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+                transferInitiatorUserHandle = Binder.getCallingUserHandle();
+                if (routerRecord != null) {
+                    transferInitiatorPackageName = routerRecord.mPackageName;
+                } else {
+                    transferInitiatorPackageName = mContext.getPackageName();
+                }
+            }
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
-                        router, oldSession, route, sessionHints);
+                requestCreateSessionWithRouter2Locked(
+                        requestId,
+                        managerRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        router,
+                        oldSession,
+                        route,
+                        sessionHints);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -399,10 +428,11 @@
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
         }
 
+        UserHandle userHandle = Binder.getCallingUserHandle();
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
+                transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -588,16 +618,28 @@
         }
     }
 
-    public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager,
-            int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+    public void requestCreateSessionWithManager(
+            @NonNull IMediaRouter2Manager manager,
+            int requestId,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(manager, "manager must not be null");
         Objects.requireNonNull(oldSession, "oldSession must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
+                requestCreateSessionWithManagerLocked(
+                        requestId,
+                        manager,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -640,18 +682,32 @@
         }
     }
 
-    public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+    public void transferToRouteWithManager(
+            @NonNull IMediaRouter2Manager manager,
+            int requestId,
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
         }
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
+        Objects.requireNonNull(transferInitiatorPackageName);
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
+                transferToRouteWithManagerLocked(
+                        requestId,
+                        manager,
+                        uniqueSessionId,
+                        route,
+                        RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -1038,9 +1094,15 @@
     }
 
     @GuardedBy("mLock")
-    private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
-            @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+    private void requestCreateSessionWithRouter2Locked(
+            int requestId,
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            @NonNull IMediaRouter2 router,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @Nullable Bundle sessionHints) {
         final IBinder binder = router.asBinder();
         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
@@ -1114,9 +1176,16 @@
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
+                obtainMessage(
+                        UserHandler::requestCreateSessionWithRouter2OnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
+                        uniqueRequestId,
+                        managerRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        routerRecord,
+                        oldSession,
+                        route,
                         sessionHints));
     }
 
@@ -1165,8 +1234,11 @@
     }
 
     @GuardedBy("mLock")
-    private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+    private void transferToRouteWithRouter2Locked(
+            @NonNull IMediaRouter2 router,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route) {
         final IBinder binder = router.asBinder();
         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
@@ -1191,9 +1263,16 @@
                             routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
         } else {
             routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::transferToRouteOnHandler,
+                    obtainMessage(
+                            UserHandler::transferToRouteOnHandler,
                             routerRecord.mUserRecord.mHandler,
-                            DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+                            DUMMY_REQUEST_ID,
+                            transferInitiatorUserHandle,
+                            routerRecord.mPackageName,
+                            routerRecord,
+                            uniqueSessionId,
+                            route,
+                            RoutingSessionInfo.TRANSFER_REASON_APP));
         }
     }
 
@@ -1416,9 +1495,13 @@
     }
 
     @GuardedBy("mLock")
-    private void requestCreateSessionWithManagerLocked(int requestId,
-            @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route) {
+    private void requestCreateSessionWithManagerLocked(
+            int requestId,
+            @NonNull IMediaRouter2Manager manager,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
         if (managerRecord == null) {
             return;
@@ -1464,9 +1547,16 @@
         // Before requesting to the provider, get session hints from the media router.
         // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
+                obtainMessage(
+                        UserHandler::requestRouterCreateSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, managerRecord, oldSession, route));
+                        uniqueRequestId,
+                        routerRecord,
+                        managerRecord,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName));
     }
 
     @GuardedBy("mLock")
@@ -1521,9 +1611,14 @@
     }
 
     @GuardedBy("mLock")
-    private void transferToRouteWithManagerLocked(int requestId,
+    private void transferToRouteWithManagerLocked(
+            int requestId,
             @NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -1541,9 +1636,16 @@
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::transferToRouteOnHandler,
+                obtainMessage(
+                        UserHandler::transferToRouteOnHandler,
                         managerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, uniqueSessionId, route));
+                        uniqueRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        routerRecord,
+                        uniqueSessionId,
+                        route,
+                        transferReason));
     }
 
     @GuardedBy("mLock")
@@ -1850,6 +1952,19 @@
             }
         }
 
+        public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
+            try {
+                mRouter.notifySessionCreated(
+                        requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "Failed to notify router of the session creation."
+                                + " Router probably died.",
+                        ex);
+            }
+        }
+
         /**
          * Sends the corresponding router an update for the given session.
          *
@@ -1857,12 +1972,27 @@
          */
         public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
             try {
-                mRouter.notifySessionInfoChanged(sessionInfo);
+                mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
             }
         }
 
+        private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
+                @NonNull RoutingSessionInfo sessionInfo) {
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+            if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
+                    || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
+                return new RoutingSessionInfo.Builder(sessionInfo)
+                        .setTransferInitiator(null, null)
+                        .build();
+            }
+
+            return sessionInfo;
+        }
+
         /**
          * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
          * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2307,9 +2437,14 @@
             return -1;
         }
 
-        private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
-                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+        private void requestRouterCreateSessionOnHandler(
+                long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
+                @NonNull ManagerRecord managerRecord,
+                @NonNull RoutingSessionInfo oldSession,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             try {
                 if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
                     // The router lacks permission to modify system routing, so we hide system
@@ -2317,7 +2452,11 @@
                     route = mSystemProvider.getDefaultRoute();
                 }
                 routerRecord.mRouter.requestCreateSessionByManager(
-                        uniqueRequestId, oldSession, route);
+                        uniqueRequestId,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
                         + "Failed to request. Router probably died.", ex);
@@ -2326,10 +2465,15 @@
             }
         }
 
-        private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
-                long managerRequestId, @NonNull RouterRecord routerRecord,
+        private void requestCreateSessionWithRouter2OnHandler(
+                long uniqueRequestId,
+                long managerRequestId,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName,
+                @NonNull RouterRecord routerRecord,
                 @NonNull RoutingSessionInfo oldSession,
-                @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+                @NonNull MediaRoute2Info route,
+                @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider == null) {
@@ -2345,8 +2489,19 @@
                             managerRequestId, oldSession, route);
             mSessionCreationRequests.add(request);
 
-            provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
-                    route.getOriginalId(), sessionHints);
+            int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP;
+            if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+                transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST;
+            }
+
+            provider.requestCreateSession(
+                    uniqueRequestId,
+                    routerRecord.mPackageName,
+                    route.getOriginalId(),
+                    sessionHints,
+                    transferReason,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         }
 
         // routerRecord can be null if the session is system's or RCN.
@@ -2386,9 +2541,14 @@
         }
 
         // routerRecord can be null if the session is system's or RCN.
-        private void transferToRouteOnHandler(long uniqueRequestId,
+        private void transferToRouteOnHandler(
+                long uniqueRequestId,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName,
                 @Nullable RouterRecord routerRecord,
-                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+                @NonNull String uniqueSessionId,
+                @NonNull MediaRoute2Info route,
+                @RoutingSessionInfo.TransferReason int transferReason) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
                     "transferring to")) {
                 return;
@@ -2399,8 +2559,13 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.transferToRoute(
+                    uniqueRequestId,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName,
+                    getOriginalId(uniqueSessionId),
+                    route.getOriginalId(),
+                    transferReason);
         }
 
         // routerRecord is null if and only if the session is created without the request, which
@@ -2535,10 +2700,8 @@
                 // session info from them.
                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
             }
-            notifySessionCreatedToRouter(
-                    matchingRequest.mRouterRecord,
-                    toOriginalRequestId(uniqueRequestId),
-                    sessionInfo);
+            matchingRequest.mRouterRecord.notifySessionCreated(
+                    toOriginalRequestId(uniqueRequestId), sessionInfo);
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2646,16 +2809,6 @@
             return true;
         }
 
-        private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
-                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
-            try {
-                routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify router of the session creation."
-                        + " Router probably died.", ex);
-            }
-        }
-
         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId) {
             try {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e562b3f..7dd1314 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -461,11 +461,24 @@
 
     // Binder call
     @Override
-    public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
-            long managerRequestId, RoutingSessionInfo oldSession,
-            MediaRoute2Info route, Bundle sessionHints) {
-        mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
-                oldSession, route, sessionHints);
+    public void requestCreateSessionWithRouter2(
+            IMediaRouter2 router,
+            int requestId,
+            long managerRequestId,
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            Bundle sessionHints,
+            @Nullable UserHandle transferInitiatorUserHandle,
+            @Nullable String transferInitiatorPackageName) {
+        mService2.requestCreateSessionWithRouter2(
+                router,
+                requestId,
+                managerRequestId,
+                oldSession,
+                route,
+                sessionHints,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
@@ -580,9 +593,20 @@
 
     // Binder call
     @Override
-    public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
-            int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
-        mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
+    public void requestCreateSessionWithManager(
+            IMediaRouter2Manager manager,
+            int requestId,
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            UserHandle transferInitiatorUserHandle,
+            String transferInitiatorPackageName) {
+        mService2.requestCreateSessionWithManager(
+                manager,
+                requestId,
+                oldSession,
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
@@ -601,9 +625,20 @@
 
     // Binder call
     @Override
-    public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
-            String sessionId, MediaRoute2Info route) {
-        mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
+    public void transferToRouteWithManager(
+            IMediaRouter2Manager manager,
+            int requestId,
+            String sessionId,
+            MediaRoute2Info route,
+            UserHandle transferInitiatorUserHandle,
+            String transferInitiatorPackageName) {
+        mService2.transferToRouteWithManager(
+                manager,
+                requestId,
+                sessionId,
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.app.ForegroundServiceDelegationOptions;
 import android.media.MediaController2;
 import android.media.Session2CommandGroup;
 import android.media.Session2Token;
@@ -89,6 +90,12 @@
     }
 
     @Override
+    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+        // TODO: Implement when MediaSession2 knows about its owner pid.
+        return null;
+    }
+
+    @Override
     public boolean isSystemPriority() {
         // System priority session is currently only allowed for telephony, so it's OK to stick to
         // the media1 API at this moment.
@@ -217,7 +224,8 @@
             synchronized (mLock) {
                 service = mService;
             }
-            service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+            service.onSessionPlaybackStateChanged(
+                    MediaSession2Record.this, playbackActive, /* playbackState= */ null);
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
     private final Context mContext;
     private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
+    private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
     private final Object mLock = new Object();
     private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
             mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
         mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
 
+        mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
         // May throw RemoteException if the session app is killed.
         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
     }
 
+    private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+        return new ForegroundServiceDelegationOptions.Builder()
+                .setClientPid(mOwnerPid)
+                .setClientUid(getUid())
+                .setClientPackageName(getPackageName())
+                .setClientAppThread(null)
+                .setSticky(false)
+                .setClientInstanceName(
+                        "MediaSessionFgsDelegate_"
+                                + getUid()
+                                + "_"
+                                + mOwnerPid
+                                + "_"
+                                + getPackageName())
+                .setForegroundServiceTypes(0)
+                .setDelegationService(
+                        ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+                .build();
+    }
+
     /**
      * Get the session binder for the {@link MediaSession}.
      *
@@ -681,6 +706,11 @@
         return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
     }
 
+    @Override
+    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+        return mForegroundServiceDelegationOptions;
+    }
+
     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
             final String callingOpPackageName, final int callingPid, final int callingUid,
             final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 mService.onSessionPlaybackStateChanged(
-                        MediaSessionRecord.this, shouldUpdatePriority);
+                        MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
 
 package com.android.server.media;
 
+import android.app.ForegroundServiceDelegationOptions;
 import android.media.AudioManager;
+import android.media.session.PlaybackState;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 
@@ -51,6 +53,15 @@
     int getUserId();
 
     /**
+     * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+     * service with changes in the {@link PlaybackState} for this session.
+     *
+     * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+     *     manager service with changes in the {@link PlaybackState} for this session.
+     */
+    ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+    /**
      * Check if this session has system priority and should receive media buttons before any other
      * sessions.
      *
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..db39b5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -59,6 +61,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -144,6 +147,7 @@
     private AudioManager mAudioManager;
     private boolean mHasFeatureLeanback;
     private ActivityManagerLocal mActivityManagerLocal;
+    private ActivityManagerInternal mActivityManagerInternal;
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
     // It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@
         mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
 
         mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     @Override
@@ -285,7 +290,8 @@
                 }
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-
+            setForegroundServiceAllowance(
+                    record, /* allowRunningInForeground= */ record.isActive());
             mHandler.postSessionsChanged(record);
         }
     }
@@ -371,8 +377,10 @@
         }
     }
 
-    void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
-            boolean shouldUpdatePriority) {
+    void onSessionPlaybackStateChanged(
+            MediaSessionRecordImpl record,
+            boolean shouldUpdatePriority,
+            @Nullable PlaybackState playbackState) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +388,10 @@
                 return;
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+            if (playbackState != null) {
+                setForegroundServiceAllowance(
+                        record, playbackState.shouldAllowServiceToRunInForeground());
+            }
         }
     }
 
@@ -543,9 +555,30 @@
         }
 
         session.close();
+        setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
         mHandler.postSessionsChanged(session);
     }
 
+    private void setForegroundServiceAllowance(
+            MediaSessionRecordImpl record, boolean allowRunningInForeground) {
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+                record.getForegroundServiceDelegationOptions();
+        if (foregroundServiceDelegationOptions == null) {
+            // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+            return;
+        }
+        if (allowRunningInForeground) {
+            mActivityManagerInternal.startForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions, /* connection= */ null);
+        } else {
+            mActivityManagerInternal.stopForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions);
+        }
+    }
+
     void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
             int callingPid, int callingUid, String callingPackage, String reason) {
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 9d151c2..f7210dd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -26,6 +27,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Utils;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
@@ -39,6 +41,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.media.flags.Flags;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -79,6 +82,10 @@
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
 
+    private final Object mTransferLock = new Object();
+    @GuardedBy("mTransferLock")
+    @Nullable private volatile SessionCreationRequest mPendingTransferRequest;
+
     SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(COMPONENT_NAME);
         mIsSystemRouteProvider = true;
@@ -146,17 +153,30 @@
     }
 
     @Override
-    public void requestCreateSession(long requestId, String packageName, String routeId,
-            Bundle sessionHints) {
+    public void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
         // a route ID different from the default route ID. The service should've filtered.
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
             return;
         }
-        if (TextUtils.equals(routeId, mSelectedRouteId)) {
-            mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
-            return;
+
+        if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            if (TextUtils.equals(routeId, mSelectedRouteId)) {
+                RoutingSessionInfo currentSessionInfo;
+                synchronized (mLock) {
+                    currentSessionInfo = mSessionInfos.get(0);
+                }
+                mCallback.onSessionCreated(this, requestId, currentSessionInfo);
+                return;
+            }
         }
 
         synchronized (mRequestLock) {
@@ -165,10 +185,23 @@
                 mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId,
                         MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
             }
-            mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId);
+            mPendingSessionCreationRequest =
+                    new SessionCreationRequest(
+                            requestId,
+                            routeId,
+                            RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
+                            transferInitiatorUserHandle,
+                            transferInitiatorPackageName);
         }
 
-        transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+        // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP.
+        transferToRoute(
+                requestId,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName,
+                SYSTEM_SESSION_ID,
+                routeId,
+                transferReason);
     }
 
     @Override
@@ -193,12 +226,31 @@
     }
 
     @Override
-    public void transferToRoute(long requestId, String sessionId, String routeId) {
+    public void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason) {
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             // The currently selected route is the default route.
             Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
             return;
         }
+
+        if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            synchronized (mTransferLock) {
+                mPendingTransferRequest =
+                        new SessionCreationRequest(
+                                requestId,
+                                routeId,
+                                transferReason,
+                                transferInitiatorUserHandle,
+                                transferInitiatorPackageName);
+            }
+        }
+
         MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
         boolean isAvailableDeviceRoute =
                 mDeviceRouteController.getAvailableRoutes().stream()
@@ -218,6 +270,11 @@
             mDeviceRouteController.transferTo(null);
             mBluetoothRouteController.transferTo(routeId);
         }
+
+        if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+                && updateSessionInfosIfNeeded()) {
+            notifySessionInfoUpdated();
+        }
     }
 
     @Override
@@ -322,9 +379,11 @@
             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
             MediaRoute2Info selectedRoute = selectedDeviceRoute;
             MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
+            List<String> transferableRoutes = new ArrayList<>();
+
             if (selectedBtRoute != null) {
                 selectedRoute = selectedBtRoute;
-                builder.addTransferableRoute(selectedDeviceRoute.getId());
+                transferableRoutes.add(selectedDeviceRoute.getId());
             }
             mSelectedRouteId = selectedRoute.getId();
             mDefaultRoute =
@@ -337,12 +396,54 @@
                 for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
                     String routeId = route.getId();
                     if (!mSelectedRouteId.equals(routeId)) {
-                        builder.addTransferableRoute(routeId);
+                        transferableRoutes.add(routeId);
                     }
                 }
             }
             for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
-                builder.addTransferableRoute(route.getId());
+                transferableRoutes.add(route.getId());
+            }
+
+            for (String route : transferableRoutes) {
+                builder.addTransferableRoute(route);
+            }
+
+            if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+                int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
+                UserHandle transferInitiatorUserHandle = null;
+                String transferInitiatorPackageName = null;
+
+                if (oldSessionInfo != null
+                        && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) {
+                    transferReason = oldSessionInfo.getTransferReason();
+                    transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
+                    transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
+                }
+
+                synchronized (mTransferLock) {
+                    if (mPendingTransferRequest != null) {
+                        boolean isTransferringToTheSelectedRoute =
+                                mPendingTransferRequest.isTargetRoute(selectedRoute);
+                        boolean canBePotentiallyTransferred =
+                                mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes);
+
+                        if (isTransferringToTheSelectedRoute) {
+                            transferReason = mPendingTransferRequest.mTransferReason;
+                            transferInitiatorUserHandle =
+                                    mPendingTransferRequest.mTransferInitiatorUserHandle;
+                            transferInitiatorPackageName =
+                                    mPendingTransferRequest.mTransferInitiatorPackageName;
+
+                            mPendingTransferRequest = null;
+                        } else if (!canBePotentiallyTransferred) {
+                            mPendingTransferRequest = null;
+                        }
+                    }
+                }
+
+                builder.setTransferReason(transferReason)
+                        .setTransferInitiator(
+                                transferInitiatorUserHandle, transferInitiatorPackageName);
             }
 
             RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
@@ -424,6 +525,22 @@
         return false;
     }
 
+    private boolean containsSelectedRouteWithId(
+            @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) {
+        if (sessionInfo == null) {
+            return false;
+        }
+
+        List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
+
+        if (selectedRoutes.size() != 1) {
+            throw new IllegalStateException("Selected routes list should contain only 1 route id.");
+        }
+
+        String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0));
+        return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId);
+    }
+
     void publishProviderState() {
         updateProviderState();
         notifyProviderState();
@@ -452,12 +569,47 @@
     }
 
     private static class SessionCreationRequest {
-        final long mRequestId;
-        final String mRouteId;
+        private final long mRequestId;
+        @NonNull private final String mRouteId;
 
-        SessionCreationRequest(long requestId, String routeId) {
-            this.mRequestId = requestId;
-            this.mRouteId = routeId;
+        @RoutingSessionInfo.TransferReason private final int mTransferReason;
+
+        @NonNull private final UserHandle mTransferInitiatorUserHandle;
+        @NonNull private final String mTransferInitiatorPackageName;
+
+        SessionCreationRequest(
+                long requestId,
+                @NonNull String routeId,
+                @RoutingSessionInfo.TransferReason int transferReason,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
+            mRequestId = requestId;
+            mRouteId = routeId;
+            mTransferReason = transferReason;
+            mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+            mTransferInitiatorPackageName = transferInitiatorPackageName;
+        }
+
+        private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
+            if (route2Info == null) {
+                return false;
+            }
+
+            return isTargetRoute(route2Info.getId());
+        }
+
+        private boolean isTargetRoute(@Nullable String routeId) {
+            return mRouteId.equals(routeId);
+        }
+
+        private boolean isInsideOfRoutesList(@NonNull List<String> routesList) {
+            for (String routeId : routesList) {
+                if (isTargetRoute(routeId)) {
+                    return true;
+                }
+            }
+
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041..e4e48bd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@
                 }
                 fout.decreaseIndent();
 
+                fout.println();
                 fout.println("Admin restricted uids for metered data:");
                 fout.increaseIndent();
                 size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@
                 }
                 fout.decreaseIndent();
 
+                fout.println();
                 fout.println("Network to interfaces:");
                 fout.increaseIndent();
                 for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@
                 fout.decreaseIndent();
 
                 fout.println();
+                fout.print("Active notifications: ");
+                fout.println(mActiveNotifs);
+
+                fout.println();
                 mStatLogger.dump(fout);
 
                 mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@
          * Build unique tag that identifies an active {@link NetworkPolicy}
          * notification of a specific type, like {@link #TYPE_LIMIT}.
          */
-        private String buildNotificationTag(NetworkPolicy policy, int type) {
+        private static String buildNotificationTag(NetworkPolicy policy, int type) {
             return TAG + ":" + policy.template.hashCode() + ":" + type;
         }
 
@@ -6683,5 +6689,10 @@
         public int getId() {
             return mId;
         }
+
+        @Override
+        public String toString() {
+            return mTag;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 135a467..ff415c1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5472,20 +5472,13 @@
         }
 
         @Override
-        public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
+        public void setAutomaticZenRuleState(String id, Condition condition) {
             Objects.requireNonNull(id, "id is null");
             Objects.requireNonNull(condition, "Condition is null");
             condition.validate();
 
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
-
-            if (android.app.Flags.modesApi()) {
-                if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
-                    throw new IllegalArgumentException(String.format(
-                            "Mismatch between fromUser (%s) and condition.source (%s)",
-                            fromUser, Condition.sourceToString(condition.source)));
-                }
-            }
+            boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
 
             mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
                     Binder.getCallingUid());
@@ -5508,7 +5501,7 @@
             if (android.app.Flags.modesApi()
                     && fromUser
                     && !isCallerSystemOrSystemUiOrShell()) {
-                throw new SecurityException(String.format(
+                throw new SecurityException(TextUtils.formatSimple(
                         "Calling %s with fromUser == true is only allowed for system", method));
             }
         }
@@ -5617,7 +5610,8 @@
             return !isCompatChangeEnabled
                     || isCallerSystemOrSystemUi()
                     || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
-                            AssociationRequest.DEVICE_PROFILE_WATCH);
+                            Set.of(AssociationRequest.DEVICE_PROFILE_WATCH,
+                                    AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION));
         }
 
         private void enforcePolicyAccess(String pkg, String method) {
@@ -10753,6 +10747,14 @@
             final String key = record.getSbn().getKey();
             final NotificationListenerService.Ranking ranking =
                     new NotificationListenerService.Ranking();
+            ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
+            ArrayList<CharSequence> smartReplies = record.getSmartReplies();
+            if (redactSensitiveNotificationsFromUntrustedListeners()
+                    && !mListeners.isUidTrusted(info.uid)
+                    && mListeners.hasSensitiveContent(record)) {
+                smartActions = null;
+                smartReplies = null;
+            }
             ranking.populate(
                     key,
                     rankings.size(),
@@ -10770,8 +10772,8 @@
                     record.isHidden(),
                     record.getLastAudiblyAlertedMs(),
                     record.getSound() != null || record.getVibration() != null,
-                    record.getSystemGeneratedSmartActions(),
-                    record.getSmartReplies(),
+                    smartActions,
+                    smartReplies,
                     record.canBubble(),
                     record.isTextChanged(),
                     record.isConversation(),
@@ -10800,7 +10802,7 @@
     }
 
     private boolean hasCompanionDevice(String pkg, @UserIdInt int userId,
-            @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) {
+            @Nullable Set</* @AssociationRequest.DeviceProfile */ String> withDeviceProfiles) {
         if (mCompanionManager == null) {
             mCompanionManager = getCompanionManager();
         }
@@ -10812,7 +10814,7 @@
         try {
             List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId);
             for (AssociationInfo association : associations) {
-                if (withDeviceProfile == null || withDeviceProfile.equals(
+                if (withDeviceProfiles == null || withDeviceProfiles.contains(
                         association.getDeviceProfile())) {
                     return true;
                 }
@@ -11521,20 +11523,16 @@
             super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
             String pkgName = getPackageName(pkgOrComponent);
             if (redactSensitiveNotificationsFromUntrustedListeners()) {
-                try {
-                    int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
-                    if (!enabled) {
-                        synchronized (mTrustedListenerUids) {
-                            mTrustedListenerUids.remove(uid);
-                        }
+                int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+                if (!enabled && uid >= 0) {
+                    synchronized (mTrustedListenerUids) {
+                        mTrustedListenerUids.remove(uid);
                     }
-                    if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
-                        synchronized (mTrustedListenerUids) {
-                            mTrustedListenerUids.add(uid);
-                        }
+                }
+                if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
+                    synchronized (mTrustedListenerUids) {
+                        mTrustedListenerUids.add(uid);
                     }
-                } catch (NameNotFoundException e) {
-                    Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
                 }
             }
 
@@ -11954,8 +11952,10 @@
 
                 for (final ManagedServiceInfo info : getServices()) {
                     boolean isTrusted = isUidTrusted(info.uid);
-                    boolean sendRedacted = isNewSensitive && !isTrusted;
-                    boolean sendOldRedacted = isOldSensitive && !isTrusted;
+                    boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+                            && isNewSensitive && !isTrusted;
+                    boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+                            && isOldSensitive && !isTrusted;
                     boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
                     boolean oldSbnVisible = (oldSbn != null)
                             && isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12054,7 +12054,7 @@
 
         StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
             if (!redactSensitiveNotificationsFromUntrustedListeners()) {
-                return sbn;
+                throw new RuntimeException("redactStatusBarNotification called while flag is off");
             }
 
             ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12226,6 +12226,7 @@
         public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
             boolean isHiddenRankingUpdate = changedHiddenNotifications != null
                     && changedHiddenNotifications.size() > 0;
+
             // TODO (b/73052211): if the ranking update changed the notification type,
             // cancel notifications for NLSes that can't see them anymore
             for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12249,7 +12250,6 @@
                 if (notifyThisListener || !isHiddenRankingUpdate) {
                     final NotificationRankingUpdate update = makeRankingUpdateLocked(
                             serviceInfo);
-
                     mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
                 }
             }
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 7f58e75e..e830d28 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -827,7 +827,8 @@
             // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
             // installer app or null for registered apps. The callback only need to send back to the
             // registered apps so we check the null condition here.
-            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList,
+                    null /* filterExtras */);
         }
     }
 
@@ -975,14 +976,16 @@
         final Bundle options = new BroadcastOptions()
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
+        BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver =
+                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+                        snapshot, callingUid, intentExtras);
         mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
                 extras, flags, null /* targetPkg */, null /* finishedReceiver */,
                 new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
-                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
-                        snapshot, callingUid, intentExtras),
+                filterExtrasForReceiver,
                 options));
         notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
     }
 
     void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
@@ -1068,9 +1071,10 @@
                                       @Nullable Bundle extras,
                                       @NonNull int[] userIds,
                                       @NonNull int[] instantUserIds,
-                                      @Nullable SparseArray<int[]> broadcastAllowList) {
+                                      @Nullable SparseArray<int[]> broadcastAllowList,
+                                      @Nullable BiFunction<Integer, Bundle, Bundle> filterExtras) {
         mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
-                instantUserIds, broadcastAllowList, mHandler);
+                instantUserIds, broadcastAllowList, mHandler, filterExtras);
     }
 
     private void notifyResourcesChanged(boolean mediaStatus,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b704..c920ca8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@
                         int returnCodeOfChild;
                         for (int childId : childUserIds) {
                             if (childId == userId) continue;
+                            if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+                                continue;
+                            }
 
                             // If package is not present in child then don't attempt to delete.
                             if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 3b9f9c8..41d0176 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.utils.WatchedArrayMap;
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 65bfb2f..b638d30 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -146,6 +146,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -154,6 +155,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
@@ -178,7 +180,6 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -672,6 +673,9 @@
                 if (pkgSetting == null || pkgSetting.getPkg() == null) {
                     return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
                 }
+                if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp())) {
+                    return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
+                }
                 if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
                     // only allow the existing package to be used if it's installed as a full
                     // application for at least one user
@@ -1167,8 +1171,9 @@
                         parseFlags);
                 archivedPackage = request.getPackageLite().getArchivedPackage();
             }
-        } catch (PackageManagerException | PackageParserException e) {
-            throw new PrepareFailure("Failed parse during installPackageLI", e);
+        } catch (PackageParserException e) {
+            throw new PrepareFailure(e.error,
+                    ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e));
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -2858,14 +2863,17 @@
                 mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
-            for (int userId : firstUserIds) {
-                // Apply restricted settings on potentially dangerous packages. Needs to happen
-                // after appOpsManager is notified of the new package
-                if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                        || request.getPackageSource()
-                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
-                    enableRestrictedSettings(packageName, request.getAppId(), userId);
-                }
+            // Apply restricted settings on potentially dangerous packages. Needs to happen
+            // after appOpsManager is notified of the new package
+            if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                    || request.getPackageSource()
+                    == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+                final int appId = request.getAppId();
+                mPm.mHandler.post(() -> {
+                    for (int userId : firstUserIds) {
+                        enableRestrictedSettings(packageName, appId, userId);
+                    }
+                });
             }
 
             // Log current value of "unknown sources" setting
@@ -3680,6 +3688,8 @@
         final ParsedPackage parsedPackage;
         try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
             parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
+        } catch (PackageParserException e) {
+            throw new PackageManagerException(e.error, e.getMessage(), e);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 187cada..e970d2c 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -51,8 +51,8 @@
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.util.Preconditions;
-import com.android.server.pm.parsing.PackageParser2;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2942bbb..cbd65a4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,6 +111,7 @@
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -120,7 +121,6 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.utils.RequestThrottle;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1d5b8c3..5225529 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,7 @@
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
@@ -221,8 +222,8 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
 import com.android.server.pm.local.PackageManagerLocalImpl;
+import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerService;
@@ -606,6 +607,8 @@
     private final boolean mIsUpgrade;
     private final boolean mIsPreNMR1Upgrade;
     private final boolean mIsPreQUpgrade;
+    // If mIsUpgrade == true, contains the prior SDK version, else -1.
+    private final int mPriorSdkVersion;
 
     // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
     // LOCK HELD.  Can be called with mInstallLock held.
@@ -1698,7 +1701,7 @@
                         () -> LocalServices.getService(UserManagerInternal.class)),
                 (i, pm) -> new DisplayMetrics(),
                 (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(),
-                        pm.mCacheDir,
+                        new PackageCacher(pm.mCacheDir),
                         pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */,
                 (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), null,
                         pm.mPackageParserCallback) /* scanningPackageParserProducer */,
@@ -1890,6 +1893,7 @@
         mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
         mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
         mIsPreQUpgrade = testParams.isPreQupgrade;
+        mPriorSdkVersion = testParams.priorSdkVersion;
         mIsUpgrade = testParams.isUpgrade;
         mMetrics = testParams.Metrics;
         mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2229,7 +2233,7 @@
                         "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
                                 + PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
             }
-
+            mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
             mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
                     mInjector.getSystemPartitions());
 
@@ -4622,7 +4626,7 @@
                 });
                 mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
                         packageName, extras, userIds, null /* instantUserIds */,
-                        broadcastAllowList, mHandler);
+                        broadcastAllowList, mHandler, null /* filterExtras */);
             }
         }
     }
@@ -7072,7 +7076,7 @@
             }
             mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
                     packageName, extras, userIds, null /* instantUserIds */,
-                    broadcastAllowList, mHandler);
+                    broadcastAllowList, mHandler, null /* filterExtras */);
         }
 
         @Override
@@ -7098,6 +7102,12 @@
             mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
                     visibilityAllowList, mHandler);
         }
+
+        @Override
+        public boolean isUpgradingFromLowerThan(int sdkVersion) {
+            final boolean isUpgrading = mPriorSdkVersion != -1;
+            return isUpgrading && mPriorSdkVersion < sdkVersion;
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index ebf1c04..049737d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -27,12 +27,12 @@
 import android.util.DisplayMetrics;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.SystemConfig;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.resolution.ComponentResolver;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 655b9c9..2d79718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -31,10 +31,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 
@@ -65,6 +65,7 @@
     public ComponentName instantAppResolverSettingsComponent;
     public boolean isPreNmr1Upgrade;
     public boolean isPreQupgrade;
+    public int priorSdkVersion = -1;
     public boolean isUpgrade;
     public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
     public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 243fb16..5724ee0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1085,8 +1085,14 @@
         // the sdk or package name along with optional additional information based on opt.
         final Map<String, List<String>> out = new HashMap<>();
         for (int userId : userIds) {
-            final int translatedUserId =
+            final int translatedUserId;
+            try {
+                translatedUserId =
                     translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
+            } catch (RuntimeException ex) {
+                getErrPrintWriter().println("Error: " + ex.toString());
+                continue;
+            }
             @SuppressWarnings("unchecked") final ParceledListSlice<PackageInfo> slice =
                     mInterface.getInstalledPackages(getFlags, translatedUserId);
             final List<PackageInfo> packages = slice.getList();
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 1bb0730..d05e4c6 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -41,6 +41,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
+import java.util.function.BiFunction;
 
 /** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
  * used by PackageMonitor to improve the broadcast latency. */
@@ -105,8 +106,9 @@
             extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
         }
         extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
-        notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
-                userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
+        notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras,
+                userIds /* userIds */, instantUserIds, broadcastAllowList, handler,
+                null /* filterExtras */);
     }
 
     public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -120,7 +122,8 @@
         String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                 : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
         notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, handler);
+                null /* instantUserIds */, null /* broadcastAllowList */, handler,
+                null /* filterExtras */);
     }
 
     public void notifyPackageChanged(String packageName, boolean dontKillApp,
@@ -137,12 +140,12 @@
             extras.putString(Intent.EXTRA_REASON, reason);
         }
         notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
-                instantUserIds, broadcastAllowList, handler);
+                instantUserIds, broadcastAllowList, handler, null /* filterExtras */);
     }
 
     public void notifyPackageMonitor(String action, String pkg, Bundle extras,
             int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
-            Handler handler) {
+            Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtras) {
         if (!isAllowedCallbackAction(action)) {
             return;
         }
@@ -160,10 +163,11 @@
 
             if (ArrayUtils.isEmpty(instantUserIds)) {
                 doNotifyCallbacksByAction(
-                        action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
+                        action, pkg, extras, resolvedUserIds, broadcastAllowList, handler,
+                        filterExtras);
             } else {
                 doNotifyCallbacksByAction(action, pkg, extras, instantUserIds, broadcastAllowList,
-                        handler);
+                        handler, filterExtras);
             }
         } catch (RemoteException e) {
             // do nothing
@@ -199,11 +203,13 @@
         synchronized (mLock) {
             callbacks = mCallbacks;
         }
-        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler);
+        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+                null /* filterExtrasFunction */);
     }
 
     private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
-            SparseArray<int[]> broadcastAllowList, Handler handler) {
+            SparseArray<int[]> broadcastAllowList, Handler handler,
+            BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
         RemoteCallbackList<IRemoteCallback> callbacks;
         synchronized (mLock) {
             callbacks = mCallbacks;
@@ -223,12 +229,13 @@
 
             final int[] allowUids =
                     broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
-            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler);
+            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
         }
     }
 
     private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
-            Intent intent, int userId, int[] allowUids, Handler handler) {
+            Intent intent, int userId, int[] allowUids, Handler handler,
+            BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
         handler.post(() -> callbacks.broadcast((callback, user) -> {
             RegisterUser registerUser = (RegisterUser) user;
             if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
@@ -236,6 +243,15 @@
                 return;
             }
             int registerUid = registerUser.getUid();
+            if (filterExtrasFunction != null) {
+                final Bundle extras = intent.getExtras();
+                if (extras != null) {
+                    final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
+                    if (filteredExtras != null) {
+                        intent.replaceExtras(filteredExtras);
+                    }
+                }
+            }
             if (allowUids != null && registerUid != Process.SYSTEM_UID
                     && !ArrayUtils.contains(allowUids, registerUid)) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 15d2fdc..1fe49c7 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -41,10 +41,11 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.InstallLocationUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.rollback.RollbackManagerInternal;
 
 import java.io.File;
@@ -399,7 +400,7 @@
             try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
                 File apexFile = new File(apexInfo.modulePath);
                 parsedPackage = packageParser.parsePackage(apexFile, 0, false);
-            } catch (PackageManagerException e) {
+            } catch (PackageParserException e) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                         "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 1089ac9..0511639 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,9 +22,10 @@
 import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ConcurrentUtils;
-import com.android.server.pm.parsing.PackageParser2;
 
 import java.io.File;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -125,6 +126,10 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        try {
+            return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        } catch (PackageParserException e) {
+            throw new PackageManagerException(e.error, e.getMessage(), e);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae273..b286b12 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@
 import android.os.Message;
 import android.os.PatternMatcher;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -3189,6 +3190,9 @@
             pkg.isScannedAsStoppedSystemApp());
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
+
+            serializer.attributeBoolean(null, "isSdkLibrary",
+                    pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
         } else {
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
@@ -4039,10 +4043,12 @@
         int targetSdkVersion = 0;
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
+        boolean isSdkLibrary = false;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
             appId = parseAppId(parser);
+            isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
             sharedUserAppId = parseSharedUserAppId(parser);
             codePathStr = parser.getAttributeValue(null, "codePath");
 
@@ -4157,7 +4163,8 @@
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Error in package manager settings: <package> has no codePath at "
                                 + parser.getPositionDescription());
-            } else if (appId > 0) {
+            } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+                    && Flags.disallowSdkLibsToBeApps())) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                         appId, pkgFlags, pkgPrivateFlags, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 79c9c8e..b6267c4 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,6 +28,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.IPackageCacher;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.ApexManager;
@@ -39,7 +40,7 @@
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class PackageCacher {
+public class PackageCacher implements IPackageCacher {
 
     private static final String TAG = "PackageCacher";
 
@@ -162,6 +163,7 @@
      * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
      * or {@code null} if no cached result exists.
      */
+    @Override
     public ParsedPackage getCachedResult(File packageFile, int flags) {
         final String cacheKey = getCacheKey(packageFile, flags);
         final File cacheFile = new File(mCacheDir, cacheKey);
@@ -192,6 +194,7 @@
     /**
      * Caches the parse result for {@code packageFile} with flags {@code flags}.
      */
+    @Override
     public void cacheResult(File packageFile, int flags, ParsedPackage parsed) {
         try {
             final String cacheKey = getCacheKey(packageFile, flags);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
new file mode 100644
index 0000000..03a7a37
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageManagerService;
+
+import java.util.Set;
+
+public class PackageParserUtils {
+    /**
+     * For parsing inside the system server but outside of {@link PackageManagerService}.
+     * Generally used for parsing information in an APK that hasn't been installed yet.
+     *
+     * This must be called inside the system process as it relies on {@link ServiceManager}.
+     */
+    @NonNull
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static PackageParser2 forParsingFileWithDefaults() {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
+                null /* cacheDir */, new PackageParser2.Callback() {
+            @Override
+            @SuppressLint("AndroidFrameworkRequiresPermission")
+            public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+                try {
+                    return platformCompat.isChangeEnabled(changeId, appInfo);
+                } catch (Exception e) {
+                    // This shouldn't happen, but assume enforcement if it does
+                    Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e);
+                    return true;
+                }
+            }
+
+            @Override
+            public boolean hasFeature(String feature) {
+                // Assume the device doesn't support anything. This will affect permission parsing
+                // and will force <uses-permission/> declarations to include all requiredNotFeature
+                // permissions and exclude all requiredFeature permissions. This mirrors the old
+                // behavior.
+                return false;
+            }
+
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+            }
+
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d3931a3..10e6edc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 
@@ -1228,6 +1229,11 @@
                         sPlatformPermissions.put(permission, permissionInfo);
                     }
                 } catch (PackageManager.NameNotFoundException ignored) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
+                                + " not found when retrieving permission info");
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
             }
@@ -1347,17 +1353,34 @@
                 // way we can avoid the datasource creating an attribution context for every call.
                 if (!(fromDatasource && current.equals(attributionSource))
                         && next != null && !current.isTrusted(context)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
+                                + current + " attribution source isn't a data source and "
+                                + current + " isn't trusted");
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 // If we already checked the permission for this one, skip the work
                 if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
                         permission, current)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
+                                + " aren't skipping permission checks and permission check returns"
+                                + " false for " + current);
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 if (next != null && !checkPermission(context, permissionManagerServiceInt,
                         permission, next)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " permission check returns false for next source " + next);
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
@@ -1402,6 +1425,10 @@
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
+                        if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                            Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+                                    + " mode is MODE_ERRORED for " + attributionSource);
+                        }
                         return PermissionChecker.PERMISSION_HARD_DENIED;
                     }
                     case AppOpsManager.MODE_IGNORED: {
@@ -1670,6 +1697,12 @@
                 final AttributionSource resolvedAttributionSource = resolveAttributionSource(
                         context, accessorSource);
                 if (resolvedAttributionSource.getPackageName() == null) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (op == OP_BLUETOOTH_CONNECT) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
+                                + "package name for " + resolvedAttributionSource + " returned"
+                                + " null");
+                    }
                     return AppOpsManager.MODE_ERRORED;
                 }
                 int notedOp = op;
@@ -1683,6 +1716,13 @@
                 if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
                     checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
                     if (checkedOpResult == MODE_ERRORED) {
+                        // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                        if (op == OP_BLUETOOTH_CONNECT) {
+                            Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                    + " checkOp for resolvedAttributionSource "
+                                    + resolvedAttributionSource + " and op " + op
+                                    + " returned MODE_ERRORED");
+                        }
                         return checkedOpResult;
                     }
                     notedOp = attributedOp;
@@ -1722,7 +1762,22 @@
                         throw new SecurityException(msg + ":" + e.getMessage());
                     }
                 }
-                return Math.max(checkedOpResult, notedOpResult);
+                int result = Math.max(checkedOpResult, notedOpResult);
+                // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+                    if (result == checkedOpResult) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " checkOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + op
+                                + " returned MODE_ERRORED");
+                    } else {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " noteOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + notedOp
+                                + " returned MODE_ERRORED");
+                    }
+                }
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3000a1c..5b13d3fe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5201,8 +5201,8 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         if ((policyFlags & FLAG_WAKE) != 0) {
             if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
                     PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 03a7bd3..3016b39 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -706,12 +706,14 @@
      * Generally, it's best to keep as little as possible in the queue thread
      * because it's the most fragile.
      * @param displayId The display ID of the motion event.
+     * @param source the {@link InputDevice} source that caused the motion.
+     * @param action the {@link MotionEvent} action for the motion.
      * @param policyFlags The policy flags associated with the motion.
      *
      * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
      */
-    int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags);
+    int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags);
 
     /**
      * Called from the input dispatcher thread before a key is dispatched to a window.
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7c833cb..6f75439 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -481,6 +481,11 @@
         protected long mTargetDurationNanos;
         protected boolean mUpdateAllowed;
         protected int[] mNewThreadIds;
+        protected boolean mPowerEfficient;
+
+        private enum SessionModes {
+            POWER_EFFICIENCY,
+        };
 
         protected AppHintSession(
                 int uid, int pid, int[] threadIds, IBinder token,
@@ -492,6 +497,7 @@
             mHalSessionPtr = halSessionPtr;
             mTargetDurationNanos = durationNanos;
             mUpdateAllowed = true;
+            mPowerEfficient = false;
             final boolean allowed = mUidObserver.isUidForeground(mUid);
             updateHintAllowed(allowed);
             try {
@@ -634,6 +640,9 @@
                 }
                 Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
                         + " greater than zero.");
+                if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
+                    mPowerEfficient = enabled;
+                }
                 mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
             }
         }
@@ -653,6 +662,12 @@
             }
         }
 
+        public boolean isPowerEfficient() {
+            synchronized (this) {
+                return mPowerEfficient;
+            }
+        }
+
         void validateWorkDuration(WorkDuration workDuration) {
             if (DEBUG) {
                 Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
@@ -718,6 +733,7 @@
                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
                 pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
+                pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1ce87a7..ca8afe1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -125,6 +125,7 @@
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -222,7 +223,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -683,6 +683,7 @@
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
+    // TODO(b/317000737): Replace it with visibility states lookup.
     int mTransitionChangeFlags;
 
     /** Whether we need to setup the animation to animate only within the letterbox. */
@@ -947,7 +948,7 @@
     private int mConfigurationSeq;
 
     /**
-     * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
+     * Temp configs used in {@link #ensureActivityConfiguration()}
      */
     private final Configuration mTmpConfig = new Configuration();
     private final Rect mTmpBounds = new Rect();
@@ -1509,7 +1510,7 @@
                 updatePictureInPictureMode(null, false);
             } else {
                 mLastReportedMultiWindowMode = inMultiWindowMode;
-                ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
+                ensureActivityConfiguration();
             }
         }
     }
@@ -1528,8 +1529,7 @@
             // precede the configuration change from the resize.
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
             mLastReportedMultiWindowMode = inPictureInPictureMode;
-            ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
-                    true /* ignoreVisibility */);
+            ensureActivityConfiguration(true /* ignoreVisibility */);
             if (inPictureInPictureMode && findMainWindow() == null) {
                 // Prevent malicious app entering PiP without valid WindowState, which can in turn
                 // result a non-touchable PiP window since the InputConsumer for PiP requires it.
@@ -3105,7 +3105,7 @@
         // {@link #returningOptions} of the activity under this one can be applied in
         // {@link #handleAlreadyVisible()}.
         if (changed || !occludesParent) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
         }
         return changed;
     }
@@ -3745,8 +3745,8 @@
             }
 
             if (ensureVisibility) {
-                mDisplayContent.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */, true /* notifyClients */);
+                mDisplayContent.ensureActivitiesVisible(null /* starting */,
+                        true /* notifyClients */);
             }
         }
 
@@ -4163,8 +4163,7 @@
         if (rootTask != null && rootTask.shouldSleepOrShutDownActivities()) {
             // Activity is always relaunched to either resumed or paused state. If it was
             // relaunched while hidden (by keyguard or smth else), it should be stopped.
-            rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                    false /* preserveWindows */);
+            rootTask.ensureActivitiesVisible(null /* starting */);
         }
     }
 
@@ -4679,14 +4678,12 @@
 
     void setShowWhenLocked(boolean showWhenLocked) {
         mShowWhenLocked = showWhenLocked;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */);
+        mAtmService.mRootWindowContainer.ensureActivitiesVisible();
     }
 
     void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
         mInheritShownWhenLocked = inheritShowWhenLocked;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */);
+        mAtmService.mRootWindowContainer.ensureActivitiesVisible();
     }
 
     /**
@@ -5468,8 +5465,13 @@
         // Defer committing visibility until transition starts.
         if (isCollecting) {
             // It may be occluded by the activity above that calls convertFromTranslucent().
-            if (!visible && mTransitionController.inPlayingTransition(this)) {
-                mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+            // Or it may be restoring transient launch to invisible when finishing transition.
+            if (!visible) {
+                if (mTransitionController.inPlayingTransition(this)) {
+                    mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+                } else if (mTransitionController.inFinishingTransition(this)) {
+                    mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+                }
             }
             return;
         }
@@ -6406,7 +6408,7 @@
         }
 
         mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
     }
 
     /**
@@ -7887,7 +7889,7 @@
     void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
             Configuration config) {
         super.applyFixedRotationTransform(info, displayFrames, config);
-        ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        ensureActivityConfiguration();
     }
 
     /**
@@ -7982,7 +7984,7 @@
         startFreezingScreen(originalDisplayRotation);
         // This activity may relaunch or perform configuration change so once it has reported drawn,
         // the screen can be unfrozen.
-        ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+        ensureActivityConfiguration();
         if (mTransitionController.isCollecting(this)) {
             // In case the task was changed from PiP but still keeps old transform.
             task.resetSurfaceControlTransforms();
@@ -8010,7 +8012,7 @@
         // the request is handled at task level with letterbox.
         if (!getMergedOverrideConfiguration().equals(
                 mLastReportedConfiguration.getMergedConfiguration())) {
-            ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+            ensureActivityConfiguration(
                     false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
             if (mTransitionController.inPlayingTransition(this)) {
                 mTransitionController.mValidateActivityCompat.add(this);
@@ -9518,14 +9520,12 @@
         return mLastReportedDisplayId != getDisplayId();
     }
 
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
-        return ensureActivityConfiguration(globalChanges, preserveWindow,
-                false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+    boolean ensureActivityConfiguration() {
+        return ensureActivityConfiguration(false /* ignoreVisibility */);
     }
 
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
-            boolean ignoreVisibility) {
-        return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+    boolean ensureActivityConfiguration(boolean ignoreVisibility) {
+        return ensureActivityConfiguration(ignoreVisibility,
                 false /* isRequestedOrientationChanged */);
     }
 
@@ -9533,9 +9533,6 @@
      * Make sure the given activity matches the current configuration. Ensures the HistoryRecord
      * is updated with the correct configuration and all other bookkeeping is handled.
      *
-     * @param globalChanges The changes to the global configuration.
-     * @param preserveWindow If the activity window should be preserved on screen if the activity
-     *                       is relaunched.
      * @param ignoreVisibility If we should try to relaunch the activity even if it is invisible
      *                         (stopped state). This is useful for the case where we know the
      *                         activity will be visible soon and we want to ensure its configuration
@@ -9545,8 +9542,8 @@
      * @return False if the activity was relaunched and true if it wasn't relaunched because we
      *         can't or the app handles the specific configuration that is changing.
      */
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
-            boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
+    boolean ensureActivityConfiguration(boolean ignoreVisibility,
+            boolean isRequestedOrientationChanged) {
         final Task rootTask = getRootTask();
         if (rootTask.mConfigWillChange) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9660,10 +9657,21 @@
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
             configChangeFlags |= changes;
-            startFreezingScreenLocked(globalChanges);
+            if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
+                    && !mTransitionController.isShellTransitionsEnabled()) {
+                startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+            }
+            final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
+                    != getWindowConfiguration().getDisplayRotation()
+                    || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
+                            getWindowConfiguration().getMaxBounds());
+            final boolean isAppResizeOnly = !displayMayChange
+                    && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
+                            | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
             // Do not preserve window if it is freezing screen because the original window won't be
             // able to update drawn state that causes freeze timeout.
-            preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
+            // TODO(b/258618073): Always preserve if possible.
+            final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
             final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
             if (hasResizeChange) {
                 final boolean isDragResizing = task.isDragResizing();
@@ -9827,11 +9835,6 @@
         return changes;
     }
 
-    private static boolean isResizeOnlyChange(int change) {
-        return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
-                | CONFIG_SCREEN_LAYOUT)) == 0;
-    }
-
     private static boolean hasResizeChange(int change) {
         return (change & (CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
                 | CONFIG_SCREEN_LAYOUT)) != 0;
@@ -9875,8 +9878,6 @@
                     task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
         }
 
-        startFreezingScreenLocked(0);
-
         try {
             ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                     (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cb2adbc..d90d017 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,7 +73,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
@@ -1733,6 +1732,7 @@
             // So disallow the transient hide activity to move itself to front, e.g. trampoline.
             if (!avoidMoveToFront() && (mService.mHomeProcess == null
                     || mService.mHomeProcess.mUid != realCallingUid)
+                    && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
                     && r.mTransitionController.isTransientHide(targetTask)) {
                 mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
             }
@@ -1859,8 +1859,7 @@
                 // over is removed.
                 // Passing {@code null} as the start parameter ensures all activities are made
                 // visible.
-                mTargetRootTask.ensureActivitiesVisible(null /* starting */,
-                        0 /* configChanges */, !PRESERVE_WINDOWS);
+                mTargetRootTask.ensureActivitiesVisible(null /* starting */);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetRootTask.mDisplayContent.executeAppTransition();
@@ -2867,7 +2866,7 @@
                 mRootWindowContainer.resumeFocusedTasksTopActivities(mTargetRootTask, null,
                         mOptions, mTransientLaunch);
             } else {
-                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+                mRootWindowContainer.ensureActivitiesVisible();
             }
         } else {
             ActivityOptions.abort(mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dbae29b..f43c1b0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -118,7 +118,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
@@ -496,16 +495,13 @@
     final UpdateConfigurationResult mTmpUpdateConfigurationResult =
             new UpdateConfigurationResult();
 
+    // TODO(b/258618073): Remove this and make the related methods return whether config is changed.
     static final class UpdateConfigurationResult {
         // Configuration changes that were updated.
         int changes;
         // If the activity was relaunched to match the new configuration.
         boolean activityRelaunched;
-
-        void reset() {
-            changes = 0;
-            activityRelaunched = false;
-        }
+        boolean mIsUpdating;
     }
 
     /** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -3834,8 +3830,7 @@
                     Settings.System.clearConfiguration(values);
                 }
                 updateConfigurationLocked(values, null, false, false /* persistent */,
-                        UserHandle.USER_NULL, false /* deferResume */,
-                        mTmpUpdateConfigurationResult);
+                        UserHandle.USER_NULL, false /* deferResume */);
                 return mTmpUpdateConfigurationResult.changes != 0;
             } finally {
                 Binder.restoreCallingIdentity(origId);
@@ -4507,12 +4502,6 @@
         }
     }
 
-    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
-            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
-        return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
-                deferResume, null /* result */);
-    }
-
     /**
      * Do either or both things: (1) change the current configuration, and (2)
      * make sure the given activity is running with the (now) current
@@ -4524,8 +4513,7 @@
      *               for that particular user
      */
     boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
-            boolean initLocale, boolean persistent, int userId, boolean deferResume,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
+            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
         int changes = 0;
         boolean kept = true;
 
@@ -4533,19 +4521,18 @@
         try {
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
+                mTmpUpdateConfigurationResult.changes = changes;
+                mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
+            mTmpUpdateConfigurationResult.mIsUpdating = false;
             continueWindowLayout();
         }
-
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
+        mTmpUpdateConfigurationResult.activityRelaunched = !kept;
         return kept;
     }
 
@@ -5325,12 +5312,10 @@
             }
 
             if (starting != null) {
-                kept = starting.ensureActivityConfiguration(changes,
-                        false /* preserveWindow */);
+                kept = starting.ensureActivityConfiguration();
                 // And we need to make sure at this point that all other activities
                 // are made visible with the correct configuration.
-                mRootWindowContainer.ensureActivitiesVisible(starting, changes,
-                        !PRESERVE_WINDOWS);
+                mRootWindowContainer.ensureActivitiesVisible(starting);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 10efb94..1872f6e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1462,7 +1462,7 @@
                 }
                 mLaunchingActivityWakeLock.release();
             }
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
         }
 
         // Atomically retrieve all of the other things to do.
@@ -1603,7 +1603,7 @@
          */
         rootTask.cancelAnimation();
         rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
-        rootTask.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
                 true /* processPausingActivities */, null /* configuration */);
 
@@ -1622,7 +1622,7 @@
             // Follow on the workaround: activities are kept force hidden till the new windowing
             // mode is set.
             rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         } finally {
             mService.continueWindowLayout();
@@ -2026,7 +2026,7 @@
 
         final Task rootTask = r.getRootTask();
         if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             // Make sure activity & window visibility should be identical
             // for all displays in this stage.
             mRootWindowContainer.executeAppTransitionForAllDisplay();
@@ -2042,7 +2042,7 @@
 
         mRecentTasks.add(task);
         mService.getTaskChangeNotificationController().notifyTaskStackChanged();
-        rootTask.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        rootTask.ensureActivitiesVisible(null /* starting */);
 
         // When launching tasks behind, update the last active time of the top task after the new
         // task has been shown briefly
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 68d13cd..6ed8967 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -91,6 +91,13 @@
     /** Non-zero if this controller is triggered by shell transition. */
     private final @TransitionOp int mTransitionOp;
 
+    /**
+     * Whether {@link #setupStartTransaction} is called when the transition is ready.
+     * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state
+     * before the transition is ready, then this controller should be finished.
+     */
+    private boolean mIsStartTransactionPrepared;
+
     /** Whether the start transaction of the transition is committed (by shell). */
     private boolean mIsStartTransactionCommitted;
 
@@ -226,7 +233,8 @@
     void updateTargetWindows() {
         if (mTransitionOp == OP_LEGACY) return;
         if (!mIsStartTransactionCommitted) {
-            if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+            if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared)
+                    && !mDisplayContent.hasTopFixedRotationLaunchingApp()
                     && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
                 Slog.d(TAG, "Cancel for no change");
                 mDisplayContent.finishAsyncRotationIfPossible();
@@ -401,9 +409,18 @@
         if (mTimeoutRunnable == null) {
             mTimeoutRunnable = () -> {
                 synchronized (mService.mGlobalLock) {
-                    Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
-                            ? " start transaction is not committed" : mTargetWindowTokens));
+                    final String reason;
                     if (!mIsStartTransactionCommitted) {
+                        if (!mIsStartTransactionPrepared) {
+                            reason = "setupStartTransaction is not called";
+                        } else {
+                            reason = "start transaction is not committed";
+                        }
+                    } else {
+                        reason = "unfinished windows " + mTargetWindowTokens;
+                    }
+                    Slog.i(TAG, "Async rotation timeout: " + reason);
+                    if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) {
                         // The transaction commit timeout will be handled by:
                         // 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
                         //    apply the start transaction of transition.
@@ -558,6 +575,7 @@
                 }
             }
         });
+        mIsStartTransactionPrepared = true;
     }
 
     /** Called when the start transition is ready, but it is not applied in time. */
@@ -577,6 +595,10 @@
     /** Called when the transition by shell is done. */
     void onTransitionFinished() {
         if (mTransitionOp == OP_CHANGE) {
+            if (mTargetWindowTokens.isEmpty()) {
+                // If nothing was handled, then complete with the transition.
+                mDisplayContent.finishAsyncRotationIfPossible();
+            }
             // With screen rotation animation, the windows are always faded in when they are drawn.
             // Because if they are drawn fast enough, the fade animation should not be observable.
             return;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c3f1e41..22d17b5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1614,7 +1614,7 @@
                 "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
         activity.mTaskSupervisor.mStoppingActivities.remove(activity);
         activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */, true);
+                true /* notifyClients */);
     }
 
     private static void restoreLaunchBehind(@NonNull ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4929df80..eed46fe 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -36,6 +36,7 @@
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.util.Objects.requireNonNull;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -152,36 +153,25 @@
     static final int BAL_ALLOW_SDK_SANDBOX = 10;
 
     static String balCodeToString(@BalCode int balCode) {
-        switch (balCode) {
-            case BAL_ALLOW_ALLOWLISTED_COMPONENT:
-                return "BAL_ALLOW_ALLOWLISTED_COMPONENT";
-            case BAL_ALLOW_ALLOWLISTED_UID:
-                return "BAL_ALLOW_ALLOWLISTED_UID";
-            case BAL_ALLOW_DEFAULT:
-                return "BAL_ALLOW_DEFAULT";
-            case BAL_ALLOW_FOREGROUND:
-                return "BAL_ALLOW_FOREGROUND";
-            case BAL_ALLOW_GRACE_PERIOD:
-                return "BAL_ALLOW_GRACE_PERIOD";
-            case BAL_ALLOW_PENDING_INTENT:
-                return "BAL_ALLOW_PENDING_INTENT";
-            case BAL_ALLOW_PERMISSION:
-                return "BAL_ALLOW_PERMISSION";
-            case BAL_ALLOW_SAW_PERMISSION:
-                return "BAL_ALLOW_SAW_PERMISSION";
-            case BAL_ALLOW_SDK_SANDBOX:
-                return "BAL_ALLOW_SDK_SANDBOX";
-            case BAL_ALLOW_VISIBLE_WINDOW:
-                return "BAL_ALLOW_VISIBLE_WINDOW";
-            case BAL_BLOCK:
-                return "BAL_BLOCK";
-            default:
-                throw new IllegalArgumentException("Unexpected value: " + balCode);
-        }
+        return switch (balCode) {
+            case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
+            case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+            case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
+            case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
+            case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
+            case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
+            case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
+            case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
+            case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+            case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
+            case BAL_BLOCK -> "BAL_BLOCK";
+            default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
+        };
     }
 
     @GuardedBy("mService.mGlobalLock")
-    private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+    private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
+            new HashMap<>();
     @GuardedBy("mService.mGlobalLock")
     private FinishedActivityEntry mTopFinishedActivity = null;
 
@@ -467,9 +457,8 @@
             return !blocks();
         }
 
-        BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+        void setOnlyCreatorAllows(boolean onlyCreatorAllows) {
             mOnlyCreatorAllows = onlyCreatorAllows;
-            return this;
         }
 
         boolean onlyCreatorAllows() {
@@ -481,10 +470,6 @@
             return this;
         }
 
-        private boolean isBasedOnRealCaller() {
-            return mBasedOnRealCaller;
-        }
-
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append(balCodeToString(mCode));
@@ -583,15 +568,14 @@
         BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
 
         if (!state.hasRealCaller()) {
-            BalVerdict resultForRealCaller = null; // nothing to compute
             if (resultForCaller.allows()) {
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Background activity start allowed. "
-                            + state.dump(resultForCaller, resultForRealCaller));
+                            + state.dump(resultForCaller, resultForCaller));
                 }
                 return statsLog(resultForCaller, state);
             }
-            return abortLaunch(state, resultForCaller, resultForRealCaller);
+            return abortLaunch(state, resultForCaller, resultForCaller);
         }
 
         // The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -653,7 +637,7 @@
                                 + " if the PI creator upgrades target_sdk to 35+"
                                 + " AND the PI sender upgrades target_sdk to 34+! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 // return the realCaller result for backwards compatibility
                 return statsLog(resultForRealCaller, state);
             }
@@ -679,7 +663,7 @@
                                 + " if the PI creator upgrades target_sdk to 35+! "
                                 + " (missing opt in by PI creator)! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 return statsLog(resultForCaller, state);
             }
             Slog.wtf(TAG,
@@ -696,7 +680,7 @@
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 return statsLog(resultForRealCaller, state);
             }
             Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -712,7 +696,7 @@
             BalVerdict resultForRealCaller) {
         Slog.w(TAG, "Background activity launch blocked! "
                 + state.dump(resultForCaller, resultForRealCaller));
-        showBalBlockedToast("BAL blocked", state);
+        showBalBlockedToast();
         return statsLog(BalVerdict.BLOCK, state);
     }
 
@@ -910,7 +894,7 @@
 
     /**
      * Check if the app allows BAL.
-     *
+     * <p>
      * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
      * String, int, boolean, boolean, boolean, long, long, long)} for details on the
      * exceptions.
@@ -1104,19 +1088,15 @@
         return true;
     }
 
-    private void showBalBlockedToast(String toastText, BalState state) {
+    private void showBalBlockedToast() {
         if (balShowToastsBlocked()) {
-            showToast(toastText
-                    + " caller:" + state.mCallingPackage
-                    + " realCaller:" + state.mRealCallingPackage);
+            showToast("BAL blocked. go/debug-bal");
         }
     }
 
-    private void showBalRiskToast(String toastText, BalState state) {
+    private void showBalRiskToast() {
         if (balShowToasts()) {
-            showToast(toastText
-                    + " caller:" + state.mCallingPackage
-                    + " realCaller:" + state.mRealCallingPackage);
+            showToast("BAL allowed in compat mode. go/debug-bal");
         }
     }
 
@@ -1281,7 +1261,7 @@
      * 2. Or top of an adjacent task fragment to (1)
      * <p>
      * The 'sourceRecord' can be considered top even if it is 'finishing'
-     *
+     * <p>
      * Returns a class where the elements are:
      * <pre>
      * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
@@ -1344,7 +1324,7 @@
     /**
      * Determines if a source is allowed to add or remove activities from the task,
      * if the current ActivityRecord is above it in the stack
-     *
+     * <p>
      * A transition is blocked ({@code false} returned) if all of the following are met:
      * <pre>
      * 1. The source activity and the current activity record belong to different apps
@@ -1489,8 +1469,8 @@
 
         if (code == BAL_ALLOW_PENDING_INTENT
                 && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
-            String activityName =
-                    intent != null ? intent.getComponent().flattenToShortString() : "";
+            String activityName = intent != null
+                    ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
             FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                     activityName,
                     BAL_ALLOW_PENDING_INTENT,
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 2e47677..e4eb7b3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -74,13 +74,13 @@
     }
 
     /**
-     * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+     * Similar to {@link #scheduleTransactionItem}, but it sends the transaction immediately and
+     * it can be called without WM lock.
      *
      * @see WindowProcessController#setReportedProcState(int)
      */
-    void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+    void scheduleTransactionItemNow(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem) throws RemoteException {
-        // Immediately dispatching to client, and must not access WMS.
         final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
         if (transactionItem.isActivityLifecycleItem()) {
             clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f8dc9c7..e7ecf52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -779,7 +779,7 @@
 
     /**
      * Used to prevent recursions when calling
-     * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
+     * {@link #ensureActivitiesVisible(ActivityRecord, boolean)}
      */
     private boolean mInEnsureActivitiesVisible = false;
 
@@ -1713,7 +1713,7 @@
         if (handled && requestingContainer instanceof ActivityRecord) {
             final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
             final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
-                    false /* deferResume */, null /* result */);
+                    false /* deferResume */);
             if (!kept) {
                 mRootWindowContainer.resumeFocusedTasksTopActivities();
             }
@@ -1721,7 +1721,7 @@
             // We have a new configuration to push so we need to update ATMS for now.
             // TODO: Clean up display configuration push between ATMS and WMS after unification.
             updateDisplayOverrideConfigurationLocked(config, null /* starting */,
-                    false /* deferResume */, null);
+                    false /* deferResume */);
         }
         return handled;
     }
@@ -6333,7 +6333,7 @@
 
         Settings.System.clearConfiguration(values);
         updateDisplayOverrideConfigurationLocked(values, null /* starting */,
-                false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+                false /* deferResume */);
         return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
     }
 
@@ -6342,8 +6342,7 @@
      * new one will be computed in WM based on current display info.
      */
     boolean updateDisplayOverrideConfigurationLocked(Configuration values,
-            ActivityRecord starting, boolean deferResume,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
+            ActivityRecord starting, boolean deferResume) {
 
         int changes = 0;
         boolean kept = true;
@@ -6361,19 +6360,19 @@
                 } else {
                     changes = performDisplayOverrideConfigUpdate(values);
                 }
+                mAtmService.mTmpUpdateConfigurationResult.changes = changes;
+                mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
+            mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
             mAtmService.continueWindowLayout();
         }
 
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
+        mAtmService.mTmpUpdateConfigurationResult.activityRelaunched = !kept;
         return kept;
     }
 
@@ -6569,8 +6568,7 @@
     }
 
 
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         if (mInEnsureActivitiesVisible) {
             // Don't do recursive work.
             return;
@@ -6579,8 +6577,7 @@
         try {
             mInEnsureActivitiesVisible = true;
             forAllRootTasks(rootTask -> {
-                rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                rootTask.ensureActivitiesVisible(starting, notifyClients);
             });
             if (mTransitionController.useShellTransitionsRotation()
                     && mTransitionController.isCollecting()
@@ -6619,7 +6616,7 @@
         if (!wasTransitionSet) {
             prepareAppTransition(TRANSIT_NONE);
         }
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+        mRootWindowContainer.ensureActivitiesVisible();
 
         // If there was a transition set already we don't want to interfere with it as we might be
         // starting it too early.
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 9cc311d..f40eb24 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -33,8 +33,6 @@
     private boolean mAboveTop;
     private boolean mContainerShouldBeVisible;
     private boolean mBehindFullyOccludedContainer;
-    private int mConfigChanges;
-    private boolean mPreserveWindows;
     private boolean mNotifyClients;
 
     EnsureActivitiesVisibleHelper(TaskFragment container) {
@@ -45,14 +43,10 @@
      * Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
      *
      * @param starting The activity that is being started
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
      * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
      *                      be sent to the clients.
      */
-    void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
-            boolean notifyClients) {
+    void reset(ActivityRecord starting, boolean notifyClients) {
         mStarting = starting;
         mTopRunningActivity = mTaskFragment.topRunningActivity();
         // If the top activity is not fullscreen, then we need to make sure any activities under it
@@ -60,33 +54,26 @@
         mAboveTop = mTopRunningActivity != null;
         mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
         mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
-        mConfigChanges = configChanges;
-        mPreserveWindows = preserveWindows;
         mNotifyClients = notifyClients;
     }
 
     /**
      * Update and commit visibility with an option to also update the configuration of visible
      * activities.
-     * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see Task#ensureActivitiesVisible(ActivityRecord)
+     * @see RootWindowContainer#ensureActivitiesVisible()
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      *
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
      * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
      *                      be sent to the clients.
      */
-    void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
-            boolean notifyClients) {
-        reset(starting, configChanges, preserveWindows, notifyClients);
+    void process(@Nullable ActivityRecord starting, boolean notifyClients) {
+        reset(starting, notifyClients);
 
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity
-                    + " configChanges=0x" + Integer.toHexString(configChanges));
+            Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity);
         }
         if (mTopRunningActivity != null && mTaskFragment.asTask() != null) {
             // TODO(14709632): Check if this needed to be implemented in TaskFragment.
@@ -107,8 +94,7 @@
             final TaskFragment childTaskFragment = child.asTaskFragment();
             if (childTaskFragment != null
                     && childTaskFragment.getTopNonFinishingActivity() != null) {
-                childTaskFragment.updateActivityVisibilities(starting, configChanges,
-                        preserveWindows, notifyClients);
+                childTaskFragment.updateActivityVisibilities(starting, notifyClients);
                 // The TaskFragment should fully occlude the activities below if the bounds
                 // equals to its parent task, unless it is translucent.
                 mBehindFullyOccludedContainer |=
@@ -188,13 +174,11 @@
             // First: if this is not the current activity being started, make
             // sure it matches the current configuration.
             if (r != mStarting && mNotifyClients) {
-                r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
-                        true /* ignoreVisibility */);
+                r.ensureActivityConfiguration(true /* ignoreVisibility */);
             }
 
             if (!r.attachedToProcess()) {
-                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
-                        resumeTopActivity && isTop, r);
+                makeVisibleAndRestartIfNeeded(mStarting, resumeTopActivity && isTop, r);
             } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
                 if (DEBUG_VISIBILITY) {
@@ -213,8 +197,6 @@
             } else {
                 r.makeVisibleIfNeeded(mStarting, mNotifyClients);
             }
-            // Aggregate current change flags.
-            mConfigChanges |= r.configChangeFlags;
         } else {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Make invisible? " + r
@@ -242,16 +224,13 @@
         }
     }
 
-    private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+    private void makeVisibleAndRestartIfNeeded(ActivityRecord starting,
             boolean andResume, ActivityRecord r) {
         // This activity needs to be visible, but isn't even running...
         // get it started and resume if no other root task in this root task is resumed.
         if (DEBUG_VISIBILITY) {
             Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
         }
-        if (r != starting) {
-            r.startFreezingScreenLocked(configChanges);
-        }
         if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8cf4713..a84ebd9 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -167,10 +167,10 @@
 
     /** {@inheritDoc} */
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
-                displayId, whenNanos, policyFlags);
+                displayId, source, action, whenNanos, policyFlags);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cbc7b83..6d11804 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -43,7 +43,6 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
@@ -239,7 +238,7 @@
         // Update the sleep token first such that ensureActivitiesVisible has correct sleep token
         // state when evaluating visibilities.
         updateKeyguardSleepToken();
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
         InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */,
                 displayId);
         setWakeTransitionReady();
@@ -291,7 +290,7 @@
 
             // Some stack visibility might change (e.g. docked stack)
             mRootWindowContainer.resumeFocusedTasksTopActivities();
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.addStartingWindowsForVisibleActivities();
             mWindowManager.executeAppTransition();
         } finally {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9305396..97cc982 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -46,6 +46,7 @@
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
@@ -1185,6 +1186,7 @@
         mUserAspectRatio = getUserMinAspectRatioOverrideCode();
 
         return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
                 && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 5269d35..7b23004 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -28,7 +28,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -126,8 +125,7 @@
                 // The activity may be relaunched if it cannot handle the current configuration
                 // changes. The activity will be paused state if it is relaunched, otherwise it
                 // keeps the original stopped state.
-                targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
-                        false /* preserveWindow */, true /* ignoreVisibility */);
+                targetActivity.ensureActivityConfiguration(true /* ignoreVisibility */);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
                         targetActivity.getConfiguration());
             }
@@ -261,7 +259,7 @@
 
             // If we updated the launch-behind state, update the visibility of the activities after
             // we fetch the visible tasks to be controlled by the animation
-            mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mService.mRootWindowContainer.ensureActivitiesVisible();
 
             ActivityOptions options = null;
             if (eventTime > 0) {
@@ -380,8 +378,7 @@
                         // transition (the target activity will be one of closing apps).
                         if (!controller.shouldDeferCancelWithScreenshot()
                                 && !targetRootTask.isFocusedRootTaskOnDisplay()) {
-                            targetRootTask.ensureActivitiesVisible(null /* starting */,
-                                    0 /* starting */, false /* preserveWindows */);
+                            targetRootTask.ensureActivitiesVisible(null /* starting */);
                         }
                         // Keep target root task in place, nothing changes, so ignore the transition
                         // logic below
@@ -389,7 +386,7 @@
                     }
 
                     mWindowManager.prepareAppTransitionNone();
-                    mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, false);
+                    mService.mRootWindowContainer.ensureActivitiesVisible();
                     mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
 
                     // No reason to wait for the pausing activity in this case, as the hiding of
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a75dae..ca66a66 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -63,7 +63,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
@@ -1753,8 +1752,7 @@
         // activities are affecting configuration now.
         // Passing null here for 'starting' param value, so that visibility of actual starting
         // activity will be properly updated.
-        ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */, false /* notifyClients */);
+        ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
 
         if (displayId == INVALID_DISPLAY) {
             // The caller didn't provide a valid display id, skip updating config.
@@ -1778,7 +1776,7 @@
         if (displayContent != null) {
             // Update the configuration of the activities on the display.
             return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
-                    deferResume, null /* result */);
+                    deferResume);
         } else {
             return true;
         }
@@ -1865,16 +1863,18 @@
      * Make sure that all activities that need to be visible in the system actually are and update
      * their configuration.
      */
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows) {
-        ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+    void ensureActivitiesVisible() {
+        ensureActivitiesVisible(null /* starting */);
+    }
+
+    void ensureActivitiesVisible(ActivityRecord starting) {
+        ensureActivitiesVisible(starting, true /* notifyClients */);
     }
 
     /**
-     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see #ensureActivitiesVisible()
      */
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         if (mTaskSupervisor.inActivityVisibilityUpdate()
                 || mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
             // Don't do recursive work.
@@ -1885,8 +1885,7 @@
             // First the front root tasks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
                 final DisplayContent display = getChildAt(displayNdx);
-                display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                display.ensureActivitiesVisible(starting, notifyClients);
             }
         } finally {
             mTaskSupervisor.endActivityVisibilityUpdate();
@@ -2237,7 +2236,7 @@
             try {
                 if (localVisibilityDeferred) {
                     mTaskSupervisor.setDeferRootVisibilityUpdate(false);
-                    ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                    ensureActivitiesVisible();
                 }
             } finally {
                 transitionController.continueTransitionReady();
@@ -2370,7 +2369,7 @@
             // It may be nothing to resume because there are pausing activities or all the top
             // activities are resumed. Then it still needs to make sure all visible activities are
             // running in case the tasks were reordered or there are non-top visible activities.
-            ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS);
+            ensureActivitiesVisible();
         }
     }
 
@@ -2542,8 +2541,7 @@
                     // display orientation can be updated first if needed. Otherwise there may
                     // have redundant configuration changes due to apply outdated display
                     // orientation (from keyguard) to activity.
-                    rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                            false /* preserveWindows */);
+                    rootTask.ensureActivitiesVisible(null /* starting */);
                 }
             });
         }
@@ -2885,8 +2883,7 @@
             if (allowDelay) {
                 result[0] &= task.goToSleepIfPossible(shuttingDown);
             } else {
-                task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        !PRESERVE_WINDOWS);
+                task.ensureActivitiesVisible(null /* starting */);
             }
         });
         return result[0];
@@ -3774,8 +3771,7 @@
                 }
             }
             if (!mHasActivityStarted) {
-                ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */);
+                ensureActivitiesVisible();
             }
             return mHasActivityStarted;
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7995028..ed54ea8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -771,6 +771,7 @@
             if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                 mService.dispatchNewAnimatorScaleLocked(this);
             }
+            mProcess.mWindowSession = this;
         }
         mAddedWindows.add(w);
     }
@@ -782,6 +783,9 @@
         }
     }
 
+    boolean hasWindow() {
+        return !mAddedWindows.isEmpty();
+    }
 
     void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
             boolean visible, int type) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbfcc22..c674176 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -90,7 +90,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_TASK_MSG;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -760,7 +759,7 @@
             return;
         }
         mResizeMode = resizeMode;
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
         mRootWindowContainer.resumeFocusedTasksTopActivities();
         updateTaskDescription();
     }
@@ -801,15 +800,14 @@
             if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
                 final ActivityRecord r = topRunningActivityLocked();
                 if (r != null) {
-                    kept = r.ensureActivityConfiguration(0 /* globalChanges */,
-                            preserveWindow);
+                    kept = r.ensureActivityConfiguration();
                     // Preserve other windows for resizing because if resizing happens when there
                     // is a dialog activity in the front, the activity that still shows some
                     // content to the user will become black and cause flickers. Note in most cases
                     // this won't cause tons of irrelevant windows being preserved because only
                     // activities in this task may experience a bounds change. Configs for other
                     // activities stay the same.
-                    mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
+                    mRootWindowContainer.ensureActivitiesVisible(r);
                     if (!kept) {
                         mRootWindowContainer.resumeFocusedTasksTopActivities();
                     }
@@ -915,7 +913,7 @@
         if (!deferResume) {
             // The task might have already been running and its visibility needs to be synchronized
             // with the visibility of the root task / windows.
-            root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            root.ensureActivitiesVisible();
             root.resumeFocusedTasksTopActivities();
         }
 
@@ -4752,7 +4750,7 @@
         }
 
         if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
     }
@@ -4793,8 +4791,7 @@
         mRootWindowContainer.resumeFocusedTasksTopActivities();
         // Update visibility of activities before notifying WM. This way it won't try to resize
         // windows that are no longer visible.
-        mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
     }
 
     final boolean isOnHomeDisplay() {
@@ -4938,41 +4935,27 @@
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link EnsureActivitiesVisibleHelper}.
-     *
      */
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows) {
-        ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting) {
+        ensureActivitiesVisible(starting, true /* notifyClients */);
     }
 
     /**
      * Ensure visibility with an option to also update the configuration of visible activities.
-     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see #ensureActivitiesVisible(ActivityRecord)
+     * @see RootWindowContainer#ensureActivitiesVisible()
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      * @param notifyClients Flag indicating whether the visibility updates should be sent to the
      *                      clients in {@link EnsureActivitiesVisibleHelper}.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link EnsureActivitiesVisibleHelper}.
      */
     // TODO: Should be re-worked based on the fact that each task as a root task in most cases.
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting, boolean notifyClients) {
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
             forAllLeafTasks(task -> {
-                task.updateActivityVisibilities(starting, configChanges, preserveWindows,
-                        notifyClients);
+                task.updateActivityVisibilities(starting, notifyClients);
             }, true /* traverseTopToBottom */);
 
             if (mTranslucentActivityWaiting != null &&
@@ -5273,7 +5256,7 @@
             // tell WindowManager that r is visible even though it is at the back of the root
             // task.
             r.setVisibility(true);
-            ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            ensureActivitiesVisible(null /* starting */);
             // If launching behind, the app will start regardless of what's above it, so mark it
             // as unknown even before prior `pause`. This also prevents a race between set-ready
             // and activityPause. Launch-behind is basically only used for dream now.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c57983c..90a3b253 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1777,13 +1777,11 @@
         void onRootTaskOrderChanged(Task rootTask);
     }
 
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
             forAllRootTasks(rootTask -> {
-                rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                rootTask.ensureActivitiesVisible(starting, notifyClients);
             });
         } finally {
             mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d01912..d425bdf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -57,7 +57,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
@@ -950,8 +949,7 @@
         }
 
         if (shouldSleep) {
-            updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
-                    !PRESERVE_WINDOWS, true /* notifyClients */);
+            updateActivityVisibilities(null /* starting */, true /* notifyClients */);
         }
 
         return shouldSleep;
@@ -1218,12 +1216,11 @@
         return top != null && top.mLaunchTaskBehind;
     }
 
-    final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    final void updateActivityVisibilities(@Nullable ActivityRecord starting,
+            boolean notifyClients) {
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
-            mEnsureActivitiesVisibleHelper.process(
-                    starting, configChanges, preserveWindows, notifyClients);
+            mEnsureActivitiesVisibleHelper.process(starting, notifyClients);
         } finally {
             mTaskSupervisor.endActivityVisibilityUpdate();
         }
@@ -1249,8 +1246,7 @@
         if (mResumedActivity == next && next.isState(RESUMED)
                 && taskDisplayArea.allResumedActivitiesComplete()) {
             // Ensure the visibility gets updated before execute app transition.
-            taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                    false /* preserveWindows */, true /* notifyClients */);
+            taskDisplayArea.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
             // Make sure we have executed any pending transitions, since there
             // should be nothing left to do at this point.
             executeAppTransition(options);
@@ -1907,7 +1903,7 @@
             prev.resumeKeyDispatchingLocked();
         }
 
-        mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible(resuming);
 
         // Notify when the task stack has changed, but only if visibilities changed (not just
         // focus). Also if there is an active root pinned task - we always want to notify it about
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f020bfa..b12855e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -43,6 +43,7 @@
 import static android.view.WindowManager.TransitionType;
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -1135,8 +1136,7 @@
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
-            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                    0 /* configChanges */, true /* preserveWindows */);
+            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible();
             // Record all the now-hiding activities so that they are committed. Just use
             // mParticipants because we can avoid a new list this way.
             for (int i = 0; i < mTransientHideTasks.size(); ++i) {
@@ -2862,8 +2862,7 @@
      * check whether to deliver the new configuration to clients.
      */
     @Nullable
-    ArrayList<ActivityRecord> applyDisplayChangeIfNeeded() {
-        ArrayList<ActivityRecord> activitiesMayChange = null;
+    void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WindowContainer<?> wc = mParticipants.valueAt(i);
             final DisplayContent dc = wc.asDisplayContent();
@@ -2880,18 +2879,13 @@
             // If the update is deferred, sendNewConfiguration won't deliver new configuration to
             // clients, then it is the caller's responsibility to deliver the changes.
             if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
-                if (activitiesMayChange == null) {
-                    activitiesMayChange = new ArrayList<>();
-                }
-                final ArrayList<ActivityRecord> visibleActivities = activitiesMayChange;
                 dc.forAllActivities(r -> {
                     if (r.isVisibleRequested()) {
-                        visibleActivities.add(r);
+                        activitiesMayChange.add(r);
                     }
                 });
             }
         }
-        return activitiesMayChange;
     }
 
     boolean getLegacyIsReady() {
@@ -3067,6 +3061,10 @@
                         Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
                         flags |= FLAG_TASK_LAUNCHING_BEHIND;
                     }
+                    if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION)
+                            == FLAGS_IS_OCCLUDED_NO_ANIMATION) {
+                        flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+                    }
                 }
                 if (task.voiceSession != null) {
                     flags |= FLAG_IS_VOICE_INTERACTION;
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 89a70e5..7b0d931 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
 
     /* Start Available Flags */
 
-    final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
-
     final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
 
     final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1310a6..dda33f3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3105,7 +3105,7 @@
         try {
             synchronized (mGlobalLock) {
                 if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
-                    mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                    mRoot.ensureActivitiesVisible();
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b99432..9e4a31c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -65,7 +65,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -571,14 +570,15 @@
         mService.deferWindowLayout();
         mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         try {
-            final ArrayList<ActivityRecord> activitiesMayChange =
-                    transition != null ? transition.applyDisplayChangeIfNeeded() : null;
-            if (activitiesMayChange != null) {
-                effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+            final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
+            if (transition != null) {
+                transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+                if (!haveConfigChanges.isEmpty()) {
+                    effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+                }
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
-            final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
             Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
                     t.getChanges().entrySet().iterator();
             while (entries.hasNext()) {
@@ -626,7 +626,7 @@
                     // When removing pip, make sure that onStop is sent to the app ahead of
                     // onPictureInPictureModeChanged.
                     // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
-                    wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                    wc.asTask().ensureActivitiesVisible(null /* starting */);
                     wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
                             null /* launchedActivity */, false /* processPausingActivities */,
                             "force-stop-on-removing-pip");
@@ -692,29 +692,16 @@
             if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
                 mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
                 // Already calls ensureActivityConfig
-                mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                mService.mRootWindowContainer.ensureActivitiesVisible();
                 mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
             } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
                 for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
                     haveConfigChanges.valueAt(i).forAllActivities(r -> {
-                        r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
-                        if (activitiesMayChange != null) {
-                            activitiesMayChange.remove(r);
+                        if (r.isVisibleRequested()) {
+                            r.ensureActivityConfiguration(true /* ignoreVisibility */);
                         }
                     });
                 }
-                // TODO(b/258618073): Combine with haveConfigChanges after confirming that there
-                //  is no problem to always preserve window. Currently this uses the parameters
-                //  as ATMS#ensureConfigAndVisibilityAfterUpdate.
-                if (activitiesMayChange != null) {
-                    for (int i = activitiesMayChange.size() - 1; i >= 0; --i) {
-                        final ActivityRecord ar = activitiesMayChange.get(i);
-                        if (!ar.isVisibleRequested()) continue;
-                        ar.ensureActivityConfiguration(0 /* globalChanges */,
-                                !PRESERVE_WINDOWS, true /* ignoreVisibility */,
-                                false /* isRequestedOrientationChanged */);
-                    }
-                }
             }
 
             if (effects != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5721750..b8fa5e5 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -188,6 +188,10 @@
     // Set to true when process was launched with a wrapper attached
     private volatile boolean mUsingWrapper;
 
+    /** Non-null if this process may have a window. */
+    @Nullable
+    Session mWindowSession;
+
     // Thread currently set for VR scheduling
     int mVrThreadTid;
 
@@ -399,7 +403,7 @@
             // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
             try {
                 // No WM lock here.
-                mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+                mAtm.getLifecycleManager().scheduleTransactionItemNow(
                         thread, configurationChangeItem);
             } catch (Exception e) {
                 Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
@@ -989,7 +993,7 @@
             if (packageName.equals(r.packageName)
                     && r.applyAppSpecificConfig(nightMode, localesOverride, gender)
                     && r.isVisibleRequested()) {
-                r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
+                r.ensureActivityConfiguration();
             }
         }
     }
@@ -1675,7 +1679,12 @@
     private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
             @NonNull ClientTransactionItem transactionItem) {
         try {
-            mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+            if (mWindowSession != null && mWindowSession.hasWindow()) {
+                mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+            } else {
+                // Non-UI process can handle the change directly.
+                mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
+            }
         } catch (DeadObjectException e) {
             // Expected if the process has been killed.
             Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
@@ -1723,7 +1732,7 @@
             overrideConfig.assetsSeq = assetSeq;
             r.onRequestedOverrideConfigurationChanged(overrideConfig);
             if (r.isVisibleRequested()) {
-                r.ensureActivityConfiguration(0, true);
+                r.ensureActivityConfiguration();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9c21e4c..58ade1b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -202,7 +202,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeReason;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -253,6 +252,7 @@
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -702,11 +702,6 @@
      */
     private final Region mTapExcludeRegion = new Region();
 
-    /**
-     * Used for testing because the real PowerManager is final.
-     */
-    private PowerManagerWrapper mPowerManagerWrapper;
-
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
@@ -1061,34 +1056,9 @@
         return mOnBackInvokedCallbackInfo;
     }
 
-    interface PowerManagerWrapper {
-        void wakeUp(long time, @WakeReason int reason, String details);
-
-        boolean isInteractive();
-
-    }
-
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
             int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
-        this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
-                ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
-                    @Override
-                    public void wakeUp(long time, @WakeReason int reason, String details) {
-                        service.mPowerManager.wakeUp(time, reason, details);
-                    }
-
-                    @Override
-                    public boolean isInteractive() {
-                        return service.mPowerManager.isInteractive();
-                    }
-                });
-    }
-
-    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
-            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
-            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mTmpTransaction = service.mTransactionFactory.get();
         mSession = s;
@@ -1106,7 +1076,6 @@
         mViewVisibility = viewVisibility;
         mPolicy = mWmService.mPolicy;
         mContext = mWmService.mContext;
-        mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
         mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
                 mActivityRecord != null
@@ -2831,12 +2800,12 @@
             boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn();
 
             if (allowTheaterMode && canTurnScreenOn
-                        && (mWmService.mAtmService.isDreaming()
-                        || !mPowerManagerWrapper.isInteractive())) {
+                    && (mWmService.mAtmService.isDreaming()
+                            || !mWmService.mPowerManager.isInteractive())) {
                 if (DEBUG_VISIBILITY || DEBUG_POWER) {
                     Slog.v(TAG, "Relayout window turning screen on: " + this);
                 }
-                mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
+                mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
             }
 
@@ -3707,7 +3676,7 @@
 
         markRedrawForSyncReported();
 
-        if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+        if (Flags.bundleClientTransactionFlag()) {
             getProcess().scheduleClientTransactionItem(
                     WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
                             mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
@@ -5669,6 +5638,12 @@
             // Skip sync for invisible app windows which are not managed by activity lifecycle.
             return false;
         }
+        if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+                && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+                && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+            // Skip sync for invisible app windows which are not managed by activity lifecycle.
+            return false;
+        }
         // In the WindowContainer implementation we immediately mark ready
         // since a generic WindowContainer only needs to wait for its
         // children to finish and is immediately ready from its own
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0dd0564..9ba0a2a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -358,8 +358,8 @@
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
     void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
-    void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
-                                       uint32_t& policyFlags) override;
+    void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+                                       nsecs_t when, uint32_t& policyFlags) override;
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                           uint32_t policyFlags) override;
     std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
@@ -1496,7 +1496,8 @@
     handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
+                                                       int32_t action, nsecs_t when,
                                                        uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
@@ -1525,7 +1526,7 @@
     const jint wmActions =
             env->CallIntMethod(mServiceObj,
                                gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
-                               displayId, when, policyFlags);
+                               displayId, source, action, when, policyFlags);
     if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
         return;
     }
@@ -2943,7 +2944,7 @@
             "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
 
     GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz,
-            "interceptMotionBeforeQueueingNonInteractive", "(IJI)I");
+            "interceptMotionBeforeQueueingNonInteractive", "(IIIJI)I");
 
     GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
             "interceptKeyBeforeDispatching",
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 8078745..3cbceec 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -632,7 +632,7 @@
             <xs:annotation name="final"/>
         </xs:element>
         <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/>
-        <xs:element name="setting" type="xs:string" minOccurs="0"/>
+        <xs:element name="setting" type="AutoBrightnessSettingName" minOccurs="0"/>
     </xs:complexType>
 
     <!-- Represents a point in the display brightness mapping, representing the lux level from the
@@ -775,4 +775,13 @@
             <xs:enumeration value="doze"/>
         </xs:restriction>
     </xs:simpleType>
+
+    <!-- Predefined auto-brighntess settings -->
+    <xs:simpleType  name="AutoBrightnessSettingName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="dim"/>
+            <xs:enumeration value="normal"/>
+            <xs:enumeration value="bright"/>
+        </xs:restriction>
+    </xs:simpleType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 91172a3..79ea274 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -23,6 +23,13 @@
     enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle;
   }
 
+  public enum AutoBrightnessSettingName {
+    method public String getRawName();
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName bright;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName dim;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName normal;
+  }
+
   public class BlockingZoneConfig {
     ctor public BlockingZoneConfig();
     method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
@@ -227,10 +234,10 @@
     ctor public LuxToBrightnessMapping();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
     method public com.android.server.display.config.AutoBrightnessModeName getMode();
-    method public String getSetting();
+    method public com.android.server.display.config.AutoBrightnessSettingName getSetting();
     method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
     method public void setMode(com.android.server.display.config.AutoBrightnessModeName);
-    method public void setSetting(String);
+    method public void setSetting(com.android.server.display.config.AutoBrightnessSettingName);
   }
 
   public class NitsMap {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
 
         // AdServicesManagerService (PP API service)
         t.traceBegin("StartAdServicesManagerService");
-        SystemService adServices = mSystemServiceManager
-                .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
-        if (adServices instanceof Dumpable) {
-            mDumper.addDumpable((Dumpable) adServices);
-        }
+        mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
         // OnDevicePersonalizationSystemService
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 03e45a2..71f5c75 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -58,6 +58,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.permission.CompatibilityPermissionInfo;
@@ -84,7 +86,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -185,7 +187,7 @@
 
     @Test
     public void test_serializePackage() throws Exception {
-        try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
+        try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) {
             AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
                     true /* useCaches */).hideAsFinal();
 
@@ -363,7 +365,7 @@
                     actualDisplayCategory = activity.getRequiredDisplayCategory();
                 }
             }
-        } catch (PackageManagerException e) {
+        } catch (PackageParserException e) {
             assertThat(e.getMessage()).contains(
                     "requiredDisplayCategory attribute can only consist"
                             + " of alphanumeric characters, '_', and '.'");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
index 8a74e24..3761240 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
 
 import junit.framework.Assert;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index b63950c..a28b28a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -38,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivityUtils;
 import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -45,7 +46,6 @@
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.test.service.server.R;
 
@@ -608,7 +608,7 @@
             try {
                 parsePackage(filename, resId, x -> x);
                 expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
-            } catch (PackageManagerException expected) {
+            } catch (PackageParserException expected) {
                 expect.that(expected.error).isEqualTo(result);
             }
         }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 98af63c..1d668cd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -18,8 +18,8 @@
 
 import android.content.pm.PackageManager
 import android.platform.test.annotations.Postsubmit
+import com.android.internal.pm.parsing.PackageParserException
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
-import com.android.server.pm.PackageManagerException
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageManagerServiceUtils
 import java.io.File
@@ -39,7 +39,7 @@
 @Postsubmit
 class SystemPartitionParseTest {
 
-    private val parser = PackageParser2.forParsingFileWithDefaults()
+    private val parser = PackageParserUtils.forParsingFileWithDefaults()
 
     @get:Rule
     val tempFolder = TemporaryFolder()
@@ -86,7 +86,7 @@
                     }
                 }
                 .mapNotNull { it.exceptionOrNull() }
-                .filterNot { (it as? PackageManagerException)?.error ==
+                .filterNot { (it as? PackageParserException)?.error ==
                         PackageManager.INSTALL_PARSE_FAILED_SKIPPED }
 
         if (exceptions.isEmpty()) return
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index f4eaa5b..fb73aff 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertEquals;
@@ -29,21 +30,22 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.MathUtils;
 import android.util.Spline;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 
 import java.util.Arrays;
 
@@ -154,15 +156,23 @@
 
     private static final float TOLERANCE = 0.0001f;
 
-    @Mock
-    DisplayWhiteBalanceController mMockDwbc;
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Before
+    public void setUp() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL);
+    }
 
     @Test
     public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
@@ -171,10 +181,10 @@
 
     @Test
     public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 1; i < LUX_LEVELS.length; i++) {
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -186,10 +196,10 @@
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         final float[] lux = { 0f, 1f };
         final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -202,10 +212,10 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         strategy.setBrightnessConfiguration(null);
         final int n = DISPLAY_LEVELS.length;
@@ -216,11 +226,11 @@
 
     @Test
     public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -235,11 +245,11 @@
 
     @Test
     public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         Spline brightnessToNits =
                 Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -254,11 +264,11 @@
 
     @Test
     public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         final float[] lux = {0f, 1f};
         final float[] nits = {
@@ -281,11 +291,11 @@
 
     @Test
     public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
         for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
             adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -326,12 +336,12 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
     }
 
@@ -342,19 +352,19 @@
         float tmp = lux[idx];
         lux[idx] = lux[idx + 1];
         lux[idx + 1] = tmp;
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // And make sure we get the same result even if it's monotone but not increasing.
         lux[idx] = lux[idx + 1];
         ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
     }
 
@@ -365,74 +375,74 @@
         // Make sure it's strictly increasing so that the only failure is the differing array
         // lengths
         lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // Extra backlight level
         final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources();
+        setUpResources();
         ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // Extra nits level
         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
         nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources();
+        setUpResources();
         ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
                 .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
     }
 
     @Test
     public void testPhysicalStrategyRequiresNitsMapping() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(physical);
     }
 
     @Test
     public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
         ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        res = createResources();
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+        setUpResources();
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
     }
 
     @Test
     public void testIdleModeConfigLoadsCorrectly() {
-        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .build();
 
         // Create an idle mode bms
         // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc,
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(mContext, ddc,
                 AUTO_BRIGHTNESS_MODE_IDLE,
-                mMockDwbc);
+                /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", bms);
 
         // Ensure that the config is the one we set
@@ -500,36 +510,31 @@
         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
     }
 
-    private Resources createResources() {
-        return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+    private void setUpResources() {
+        setUpResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
     }
 
-    private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-
-        Resources mockResources = mock(Resources.class);
+    private void setUpResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
         if (luxLevelsIdle.length > 0) {
             int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
                     luxLevelsIdle.length);
-            when(mockResources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
-                    .thenReturn(luxLevelsIdleResource);
+            mContext.getOrCreateTestableResources().addOverride(
+                    com.android.internal.R.array.config_autoBrightnessLevelsIdle,
+                    luxLevelsIdleResource);
         }
 
         TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
-        when(mockResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
-                .thenReturn(mockBrightnessLevelNitsIdle);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle,
+                mockBrightnessLevelNitsIdle);
 
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
-                .thenReturn(1);
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
-                .thenReturn(255);
-        when(mockResources.getFraction(
-                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
-                .thenReturn(MAXIMUM_GAMMA);
-        return mockResources;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum, 1);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum, 255);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
+                MAXIMUM_GAMMA);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
@@ -570,11 +575,11 @@
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
 
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Let's start with a validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -600,11 +605,11 @@
         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -627,11 +632,11 @@
     public void testGammaCorrectionExtremeChangeAtCenter() {
         // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
         // just make sure the adjustment reflects the change.
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
         strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f);
         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -650,11 +655,11 @@
         final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Validity, as per tradition:
         assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -679,15 +684,33 @@
 
     @Test
     public void testGetMode() {
-        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_IDLE,
-                mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_IDLE, /* displayWhiteBalanceController= */ null);
         assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
     }
 
+    @Test
+    public void testAutoBrightnessModeAndPreset() {
+        int mode = AUTO_BRIGHTNESS_MODE_DOZE;
+        int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset);
+
+        setUpResources();
+        DisplayDeviceConfig ddc = new DdcBuilder()
+                .setAutoBrightnessLevels(mode, preset, DISPLAY_LEVELS)
+                .setAutoBrightnessLevelsLux(mode, preset, LUX_LEVELS).build();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, mode,
+                /* displayWhiteBalanceController= */ null);
+        assertNotNull("BrightnessMappingStrategy should not be null", simple);
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
+        }
+    }
+
     private static class DdcBuilder {
         private DisplayDeviceConfig mDdc;
 
@@ -695,10 +718,11 @@
             mDdc = mock(DisplayDeviceConfig.class);
             when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
             when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
-            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT))
-                    .thenReturn(LUX_LEVELS);
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(LUX_LEVELS);
             when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
-            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT))
+            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
                     .thenReturn(EMPTY_FLOAT_ARRAY);
         }
 
@@ -713,8 +737,15 @@
         }
 
         DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
-            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT))
-                    .thenReturn(luxLevels);
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(luxLevels);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevelsLux(
+                @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+                float[] luxLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(mode, preset)).thenReturn(luxLevels);
             return this;
         }
 
@@ -724,7 +755,16 @@
         }
 
         DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
-            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT))
+            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+                    .thenReturn(brightnessLevels);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevels(
+                @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+                float[] brightnessLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevels(mode, preset))
                     .thenReturn(brightnessLevels);
             return this;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 61c6076..7a84406 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.config.SensorData.SupportedMode;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -41,6 +42,7 @@
 import android.content.res.TypedArray;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Temperature;
+import android.provider.Settings;
 import android.util.SparseArray;
 import android.util.Spline;
 import android.view.SurfaceControl;
@@ -609,10 +611,12 @@
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(),
                 new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
-                AUTO_BRIGHTNESS_MODE_DEFAULT), new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+                new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
-                AUTO_BRIGHTNESS_MODE_DEFAULT), new float[]{brightnessIntToFloat(50),
-                brightnessIntToFloat(100), brightnessIntToFloat(150)}, SMALL_DELTA);
+                AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+                new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+                        brightnessIntToFloat(150)}, SMALL_DELTA);
 
         // Test thresholds
         assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -739,31 +743,39 @@
 
         assertArrayEquals(new float[]{0.0f, 80},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
-                        AUTO_BRIGHTNESS_MODE_DEFAULT), ZERO_DELTA);
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{0.2f, 0.3f},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
-                        AUTO_BRIGHTNESS_MODE_DEFAULT), SMALL_DELTA);
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 90},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
         assertArrayEquals(new float[]{0.3f, 0.4f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 95},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{0.35f, 0.45f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 100},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
         assertArrayEquals(new float[]{0.4f, 0.5f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
     }
 
     @Test
@@ -776,10 +788,12 @@
         assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
                         brightnessIntToFloat(150)},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
-                        AUTO_BRIGHTNESS_MODE_DEFAULT), SMALL_DELTA);
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
         assertArrayEquals(new float[]{0, 110, 500},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
-                        AUTO_BRIGHTNESS_MODE_DEFAULT), ZERO_DELTA);
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{2, 200, 600},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index ffdc8b4..4cc68cf 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -1905,7 +1905,7 @@
         }
 
         @Override
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 64cdac4..943862f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1669,7 +1669,7 @@
         }
 
         @Override
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
index 49fa254..dafbbb3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
@@ -23,8 +23,10 @@
 import org.junit.Test
 import org.mockito.junit.MockitoJUnit
 
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
+import java.util.concurrent.Executor
 
 @SmallTest
 class DisplayPowerStateTest {
@@ -36,15 +38,21 @@
 
     private val mockBlanker = mock<DisplayBlanker>()
     private val mockColorFade = mock<ColorFade>()
+    private val mockExecutor = mock<Executor>()
 
     @Before
     fun setUp() {
-        displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON)
+        displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON,
+                mockExecutor)
     }
 
     @Test
     fun `destroys ColorFade on stop`() {
         displayPowerState.stop()
+        val runnableCaptor = argumentCaptor<Runnable>()
+
+        verify(mockExecutor).execute(runnableCaptor.capture())
+        runnableCaptor.firstValue.run()
 
         verify(mockColorFade).destroy()
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 8d8274c..87fc7a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -122,6 +122,16 @@
     }
 
     @Test
+    public void testRegisterHdrListener_ZeroMinHdrPercent() {
+        IBinder otherBinder = mock(IBinder.class);
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT,
+            /* minimumHdrPercentOfScreen= */ 0, otherBinder);
+
+        verify(mMockHdrInfoListener).unregister(mMockBinder);
+        verify(mMockHdrInfoListener).register(otherBinder);
+    }
+
+    @Test
     public void testRegisterNotCalledIfHbmConfigIsMissing() {
         IBinder otherBinder = mock(IBinder.class);
         mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003d..32878b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.flags.Flags;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.Log;
@@ -97,6 +100,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@
     private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
     private static final String MISSING_PERMISSION = "missing_permission";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Random mRandom;
 
     @Mock
@@ -1347,6 +1354,24 @@
         assertThat(mManager.isVisibleToCaller()).isFalse();
     }
 
+    @Test
+    public void testValidateLocation_futureLocation() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+        Location location = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(location);
+
+        assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(location);
+
+        Location futureLocation = createLocation(NAME, mRandom);
+        futureLocation.setElapsedRealtimeNanos(
+                SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+        mProvider.setProviderLocation(futureLocation);
+
+        assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(location);
+    }
+
     @MediumTest
     @Test
     public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index c2b52b4..57326b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -50,9 +50,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Before;
@@ -175,7 +176,7 @@
                     mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false);
             // isUpdatedSystemApp is ignoreable above, only used for shared library adjustment
             return parsedPackage.hideAsFinal();
-        } catch (PackageManagerException e) {
+        } catch (PackageParserException e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 7b29e2a..538c0ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -56,6 +56,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
 import com.android.internal.R
+import com.android.internal.pm.parsing.PackageParser2
 import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.internal.pm.pkg.parsing.ParsingPackage
@@ -69,7 +70,6 @@
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.dex.DexManager
 import com.android.server.pm.dex.DynamicCodeLogger
-import com.android.server.pm.parsing.PackageParser2
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.resolution.ComponentResolver
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index da929af..7feafef 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -21,6 +21,7 @@
 import android.os.Build
 import android.os.Process
 import android.util.Log
+import com.android.internal.pm.parsing.PackageParserException
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.testutils.whenever
 import java.io.File
@@ -120,7 +121,7 @@
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
                 anyBoolean()))
-                .thenThrow(PackageManagerException(
+                .thenThrow(PackageParserException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
         verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 6c44fd0..60cedcf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -44,6 +44,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.function.BiFunction;
 
 /**
  * A unit test for PackageMonitorCallbackHelper implementation.
@@ -78,7 +79,8 @@
 
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -91,7 +93,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
-                null /* broadcastAllowList */, mHandler);
+                null /* broadcastAllowList */, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
 
@@ -99,12 +101,41 @@
         mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
 
     @Test
+    public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
+        Bundle result = new Bundle();
+        result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        result.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[]{FAKE_PACKAGE_NAME});
+        BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> result;
+
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+                null /* broadcastAllowList */, mHandler, filterExtras);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGES_SUSPENDED);
+        String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+        assertThat(pkgList).isNotNull();
+        assertThat(pkgList.length).isEqualTo(1);
+        assertThat(pkgList[0]).isEqualTo(FAKE_PACKAGE_NAME);
+    }
+
+    @Test
     public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
         IRemoteCallback callback = createMockPackageMonitorCallback();
 
@@ -112,7 +143,8 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -136,7 +168,8 @@
         // Notify for user 10
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -239,7 +272,8 @@
         mPackageMonitorCallbackHelper.onUserRemoved(10);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -255,7 +289,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
@@ -271,7 +305,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -287,7 +321,7 @@
                 Process.SYSTEM_UID);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 2d36ff3..d49bc43 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -7,3 +7,4 @@
 per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
 per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
 per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
+per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c9e1c4a..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,21 +16,30 @@
 
 package com.android.server.biometrics.sensors.face;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -42,6 +51,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.biometrics.Flags;
@@ -66,6 +76,7 @@
     private static final int ID_VIRTUAL = 6;
     private static final String NAME_DEFAULT = "default";
     private static final String NAME_VIRTUAL = "virtual";
+    private static final String OP_PACKAGE_NAME = "FaceServiceTest/SystemUi";
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -78,15 +89,19 @@
     @Rule
     public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
     @Mock
-    FaceProvider mFaceProviderDefault;
+    private FaceProvider mFaceProviderDefault;
     @Mock
-    FaceProvider mFaceProviderVirtual;
+    private FaceProvider mFaceProviderVirtual;
     @Mock
-    IFace mDefaultFaceDaemon;
+    private IFace mDefaultFaceDaemon;
     @Mock
-    IFace mVirtualFaceDaemon;
+    private IFace mVirtualFaceDaemon;
     @Mock
-    IBiometricService mIBiometricService;
+    private IBiometricService mIBiometricService;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private IFaceServiceReceiver mFaceServiceReceiver;
 
     private final SensorProps mDefaultSensorProps = new SensorProps();
     private final SensorProps mVirtualSensorProps = new SensorProps();
@@ -117,7 +132,13 @@
                 new SensorProps[]{mVirtualSensorProps});
         when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
         when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+        when(mFaceProviderDefault.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+        when(mFaceProviderVirtual.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
 
+        mContext.getTestablePermissions().setPermission(
+                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
         mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
                 (name) -> {
@@ -136,7 +157,13 @@
                     if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
                     if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
                     return null;
-                }, () -> mIBiometricService);
+                }, () -> mIBiometricService,
+                (name) -> {
+                    if (NAME_DEFAULT.equals(name)) return mFaceProviderDefault;
+                    if (NAME_VIRTUAL.equals(name)) return mFaceProviderVirtual;
+                    return null;
+                },
+                () -> new String[]{NAME_DEFAULT, NAME_VIRTUAL});
     }
 
     @Test
@@ -191,6 +218,39 @@
                 eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
     }
 
+    @Test
+    public void testOptionsForAuthentication() throws Exception {
+        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+                .build();
+        initService();
+        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
+
+        final long operationId = 5;
+        mFaceService.mServiceWrapper.authenticate(mToken, operationId, mFaceServiceReceiver,
+                faceAuthenticateOptions);
+
+        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
+    @Test
+    public void testOptionsForDetect() throws Exception {
+        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+                .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
+                        .getPackageName())
+                .build();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.string.config_keyguardComponent,
+                OP_PACKAGE_NAME);
+        initService();
+        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
+        mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
+                faceAuthenticateOptions);
+
+        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
     private void waitForRegistration() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index f570ba2..88956b6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -61,6 +62,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -92,6 +94,7 @@
     private static final String NAME_VIRTUAL = "virtual";
     private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
             List.of();
+    private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -343,6 +346,24 @@
         assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
     }
 
+    @Test
+    public void testOptionsForDetect() throws Exception {
+        FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+                new FingerprintAuthenticateOptions.Builder()
+                        .setOpPackageName(ComponentName.unflattenFromString(
+                                OP_PACKAGE_NAME).getPackageName())
+                        .build();
+
+        mContext.getOrCreateTestableResources().addOverride(
+                R.string.config_keyguardComponent,
+                OP_PACKAGE_NAME);
+        initServiceWithAndWait(NAME_DEFAULT);
+        mService.mServiceWrapper.detectFingerprint(mToken, mServiceReceiver,
+                fingerprintAuthenticateOptions);
+
+        assertThat(fingerprintAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
 
     private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
             FingerprintProvider provider, long operationId) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 071d571..9b28b81 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -53,11 +53,11 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class VirtualCameraControllerTest {
 
-    private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+    private static final String CAMERA_NAME_1 = "Virtual camera 1";
     private static final int CAMERA_WIDTH_1 = 100;
     private static final int CAMERA_HEIGHT_1 = 200;
 
-    private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+    private static final String CAMERA_NAME_2 = "Virtual camera 2";
     private static final int CAMERA_WIDTH_2 = 400;
     private static final int CAMERA_HEIGHT_2 = 600;
     private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
@@ -84,7 +84,7 @@
     @Test
     public void registerCamera_registersCamera() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
 
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -98,7 +98,7 @@
     @Test
     public void unregisterCamera_unregistersCamera() throws Exception {
         VirtualCameraConfig config = createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
         mVirtualCameraController.registerCamera(config);
 
         mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +109,9 @@
     @Test
     public void close_unregistersAllCameras() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
+                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
 
         mVirtualCameraController.close();
 
@@ -129,10 +129,10 @@
     }
 
     private VirtualCameraConfig createVirtualCameraConfig(
-            int width, int height, int format, int displayNameResId) {
+            int width, int height, int format, String displayName) {
         return new VirtualCameraConfig.Builder()
                 .addStreamConfig(width, height, format)
-                .setDisplayNameStringRes(displayNameResId)
+                .setName(displayName)
                 .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
                 .build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index a63d01b..e7da26e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -456,6 +456,14 @@
     }
 
     @Test
+    public void avbEnabled_standby_avbDisabled() {
+        enableAbsoluteVolumeBehavior();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+    }
+
+    @Test
     public void avbEnabled_cecVolumeDisabled_avbDisabled() {
         enableAbsoluteVolumeBehavior();
 
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index ce15c6d..eb9cce0 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,10 +67,10 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.integrity.engine.RuleEvaluationEngine;
 import com.android.server.integrity.model.IntegrityCheckResult;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.testutils.TestUtils;
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 1bfd43f..25eedf5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -37,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
@@ -161,7 +162,8 @@
     }
 
     @Test
-    public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException {
+    public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException,
+            PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk");
         ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
@@ -178,7 +180,7 @@
 
     @Test
     public void testParsePackageSplitsWithDmFileValid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageManagerException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
@@ -201,7 +203,7 @@
 
     @Test
     public void testParsePackageSplitsNoBaseWithDmFileValid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageManagerException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_feature_a.apk");
@@ -219,7 +221,7 @@
     }
 
     @Test
-    public void testParsePackageWithDmFileInvalid() throws IOException {
+    public void testParsePackageWithDmFileInvalid() throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
         Files.createFile(invalidDmFile.toPath());
@@ -242,7 +244,7 @@
 
     @Test
     public void testParsePackageSplitsWithDmFileInvalid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
@@ -268,7 +270,7 @@
 
     @Test
     public void testParsePackageWithDmFileInvalidManifest()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
 
@@ -283,7 +285,7 @@
 
     @Test
     public void testParsePackageWithDmFileEmptyManifest()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
                 /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
@@ -299,7 +301,7 @@
 
     @Test
     public void testParsePackageWithDmFileBadPackageName()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
                 DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -315,7 +317,7 @@
 
     @Test
     public void testParsePackageWithDmFileBadVersionCode()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
                 /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -331,7 +333,7 @@
 
     @Test
     public void testParsePackageWithDmFileMissingPackageName()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
                 DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -347,7 +349,7 @@
 
     @Test
     public void testParsePackageWithDmFileMissingVersionCode()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
                 /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 9b4ca2a..6a088d9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -36,7 +36,7 @@
 @Postsubmit
 class AndroidPackageParsingValidationTest {
     companion object {
-        private val parser2 = PackageParser2.forParsingFileWithDefaults()
+        private val parser2 = PackageParserUtils.forParsingFileWithDefaults()
         private val apks = ((PackageManagerService.SYSTEM_PARTITIONS)
                 .flatMap {
                     listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder)
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
index c44f583..e420e4b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -18,11 +18,12 @@
 
 import android.content.pm.ApplicationInfo
 import android.util.ArraySet
+import com.android.internal.pm.parsing.PackageParser2
 import java.io.File
 
 class TestPackageParser2(var cacheDir: File? = null) : PackageParser2(
         null /* separateProcesses */, null /* displayMetrics */,
-        cacheDir /* cacheDir */, object : PackageParser2.Callback() {
+    cacheDir?.let { PackageCacher(cacheDir) }, object : PackageParser2.Callback() {
     override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean {
         return true
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 2868b7e..ae36839 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -68,10 +68,7 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.INotificationListener;
 import android.service.notification.NotificationListenerFilter;
 import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@
 public class NotificationListenersTest extends UiServiceTestCase {
 
     @Rule
-    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock
     private PackageManager mPm;
@@ -696,8 +693,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testListenerTrusted_withPermission() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
                 .thenReturn(PERMISSION_GRANTED);
         ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testListenerTrusted_withSystemSignature() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
         ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
         mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testListenerTrusted_withCdmAssociation() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
         AssociationInfo assocInfo = mock(AssociationInfo.class);
         when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testListenerTrusted_ifFlagDisabled() {
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
         mListeners.onServiceAdded(info);
         assertTrue(mListeners.isUidTrusted(mUid1));
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testRedaction_whenPosted() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
         infos.add(getMockServiceInfo());
         doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@
         mListeners.notifyPostedLocked(r, old);
         verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
         verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));
-
-
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testRedaction_whenPosted_oldRemoved() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
         infos.add(getMockServiceInfo());
         doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testRedaction_whenRemoved() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         doReturn(mock(StatusBarNotification.class))
                 .when(mListeners).redactStatusBarNotification(any());
         ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testRedaction_noneIfFlagDisabled() {
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
         infos.add(getMockServiceInfo());
         doReturn(infos).when(mListeners).getServices();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a0e49a6..3ab7496 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,7 +77,9 @@
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
 import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.Condition.SOURCE_CONTEXT;
@@ -216,9 +218,6 @@
 import android.os.WorkSource;
 import android.permission.PermissionManager;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.rule.DeniedDevices;
 import android.platform.test.rule.DeviceProduct;
@@ -361,9 +360,6 @@
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
-    @Rule
-    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private TestableNotificationManagerService mService;
     private INotificationManager mBinderService;
     private NotificationManagerInternal mInternalService;
@@ -9262,41 +9258,46 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+    public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
 
-        Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
-                SOURCE_CONTEXT);
-        mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
-        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
-
         Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
                 SOURCE_USER_ACTION);
-        mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+        mBinderService.setAutomaticZenRuleState("id", withSourceUser);
+
         verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
                 eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+    public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
             throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
 
         Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
                 SOURCE_CONTEXT);
-        assertThrows(IllegalArgumentException.class,
-                () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
-                        /* fromUser= */ true));
+        mBinderService.setAutomaticZenRuleState("id", withSourceContext);
 
-        Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
-                SOURCE_USER_ACTION);
-        assertThrows(IllegalArgumentException.class,
-                () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
-                        /* fromUser= */ false));
+        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+            throws Exception {
+        ZenModeHelper zenModeHelper = setUpMockZenTest();
+        mService.isSystemUid = true;
+
+        Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+                SOURCE_CONTEXT);
+        mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
     }
 
     private ZenModeHelper setUpMockZenTest() {
@@ -11778,8 +11779,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         NotificationRecord r =
                 generateNotificationRecord(mTestNotificationChannel, 0, 0);
         mService.addNotification(r);
@@ -11808,12 +11809,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
     public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         NotificationRecord r =
                 generateNotificationRecord(mTestNotificationChannel, 0, 0);
-        mService.addNotification(r);
-        mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+        when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
         when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
         when(mListeners.hasSensitiveContent(any())).thenReturn(true);
         StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11993,6 +11993,97 @@
     }
 
     @Test
+    public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+        when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        addSmartActionsAndReplies(pkgA);
+        mService.addNotification(pkgA);
+        NotificationRecord pkgB = new NotificationRecord(mContext,
+                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+        addSmartActionsAndReplies(pkgB);
+        mService.addNotification(pkgB);
+
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+        assertEquals(0, ranking.getSmartActions().size());
+        assertEquals(0, ranking.getSmartReplies().size());
+        NotificationListenerService.Ranking ranking2 =
+                nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
+        assertEquals(0, ranking2.getSmartActions().size());
+        assertEquals(0, ranking2.getSmartReplies().size());
+    }
+
+    @Test
+    public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+        when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        addSmartActionsAndReplies(pkgA);
+
+        mService.addNotification(pkgA);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+        assertEquals(1, ranking.getSmartActions().size());
+        assertEquals(1, ranking.getSmartReplies().size());
+    }
+
+    @Test
+    public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        addSmartActionsAndReplies(pkgA);
+
+        mService.addNotification(pkgA);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+
+        // No sensitive content, no redaction
+        when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
+        when(mListeners.hasSensitiveContent(any())).thenReturn(false);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+        assertEquals(1, ranking.getSmartActions().size());
+        assertEquals(1, ranking.getSmartReplies().size());
+
+        // trusted listener, no redaction
+        when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        nru = mService.makeRankingUpdateLocked(info);
+        ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+        assertEquals(1, ranking.getSmartActions().size());
+        assertEquals(1, ranking.getSmartReplies().size());
+    }
+
+    private void addSmartActionsAndReplies(NotificationRecord record) {
+        Bundle b = new Bundle();
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        actions.add(new Notification.Action(0, "", null));
+        b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
+        ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
+        b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
+        Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
+                b, "", record.getUserId());
+        record.addAdjustment(a);
+        record.applyAdjustments();
+    }
+
+    @Test
     public void testMaybeShowReviewPermissionsNotification_flagOff() {
         mService.setShowReviewPermissionsNotification(false);
         reset(mMockNm);
@@ -13616,7 +13707,31 @@
 
     @Test
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
-    public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+    public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_WATCH, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setNotificationPolicy_autoCompanionApp_setsGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setNotificationPolicy_otherCompanionApp_doesNotSetGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+    }
+
+    private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+            @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+            throws RemoteException {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mService.setCallerIsNormalPackage();
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -13626,14 +13741,19 @@
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
                 .thenReturn(ImmutableList.of(
                         new AssociationInfo.Builder(1, mUserId, "package")
-                                .setDisplayName("My watch")
-                                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+                                .setDisplayName("My connected device")
+                                .setDeviceProfile(deviceProfile)
                                 .build()));
 
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy, false);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+        if (canSetGlobalPolicy) {
+            verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+        } else {
+            verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+                    eq(policy), anyInt());
+        }
     }
 
     @Test
@@ -13703,7 +13823,29 @@
 
     @Test
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
-    public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+    public void setInterruptionFilter_watchCompanionApp_setsGlobalZen() throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_WATCH, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setInterruptionFilter_autoCompanionApp_setsGlobalZen() throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setInterruptionFilter_otherCompanionApp_doesNotSetGlobalZen()
+            throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+    }
+
+    private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+            @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+            throws RemoteException {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = zenModeHelper;
@@ -13713,14 +13855,19 @@
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
                 .thenReturn(ImmutableList.of(
                         new AssociationInfo.Builder(1, mUserId, "package")
-                                .setDisplayName("My watch")
-                                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+                                .setDisplayName("My connected device")
+                                .setDeviceProfile(deviceProfile)
                                 .build()));
 
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
 
-        verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+        if (canSetGlobalPolicy) {
+            verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+                    eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+        } else {
+            verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+                    eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+        }
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 85c6f9e..e88a00b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -161,6 +161,7 @@
 
 import com.android.internal.R;
 import com.android.server.wm.ActivityRecord.State;
+import com.android.window.flags.Flags;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -202,8 +203,7 @@
     }
 
     private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
-        return new TestStartingWindowOrganizer(mAtm,
-                mSystemServicesTestRule.getPowerManagerWrapper());
+        return new TestStartingWindowOrganizer(mAtm);
     }
 
     @Test
@@ -321,7 +321,7 @@
     }
 
     private void ensureActivityConfiguration(ActivityRecord activity) {
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
     }
 
     @Test
@@ -719,7 +719,7 @@
 
         // Clear size compat.
         activity.clearSizeCompatMode();
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
         mDisplayContent.sendNewConfiguration();
 
         // Relaunching the app should still respect the orientation request.
@@ -820,8 +820,7 @@
 
             task.onConfigurationChanged(newConfig);
 
-            activity.ensureActivityConfiguration(0 /* globalChanges */,
-                    false /* preserveWindow */, true /* ignoreVisibility */);
+            activity.ensureActivityConfiguration(true /* ignoreVisibility */);
 
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(activity.token,
@@ -1564,8 +1563,7 @@
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
         topActivity.setShowWhenLocked(true);
 
         // Verify the stack-top activity is occluded keyguard.
@@ -1625,7 +1623,6 @@
         secondActivity.finishing = true;
         secondActivity.completeFinishing("test");
         verify(secondActivity.mDisplayContent).ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */ , false /* preserveWindows */,
                 true /* notifyClients */);
 
         // Finish the first activity
@@ -1633,7 +1630,6 @@
         firstActivity.setVisibleRequested(true);
         firstActivity.completeFinishing("test");
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */ , false /* preserveWindows */,
                 true /* notifyClients */);
 
         // Remove the translucent activity and clear invocations for next test
@@ -1961,6 +1957,7 @@
         display.continueUpdateOrientationForDiffOrienLaunchingApp();
         assertTrue(display.isFixedRotationLaunchingApp(activity));
 
+        activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
         // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
         // remote rotation is completed.
         doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -1971,14 +1968,12 @@
         activity.finishFixedRotationTransform();
         final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
         assertNotNull(rotationAnim);
-        rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
 
         // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
         // rotation. There should be a rotation animation to cover the change of activity.
         verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
         assertTrue(activity.isFreezingScreen());
         assertFalse(displayRotation.isRotatingSeamlessly());
-        assertTrue(rotationAnim.isRotating());
 
         // Simulate the remote rotation has completed and the configuration doesn't change, then
         // the rotated activity should also be restored by clearing the transform.
@@ -3371,7 +3366,7 @@
         // to client if the app didn't request IME visible.
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
-        if (mWm.mFlags.mWindowStateResizeItemFlag) {
+        if (Flags.bundleClientTransactionFlag()) {
             verify(app2.getProcess()).scheduleClientTransactionItem(
                     isA(WindowStateResizeItem.class));
         } else {
@@ -3698,7 +3693,7 @@
         doReturn(false).when(activity).showToCurrentUser();
         spyOn(taskFragment);
         doReturn(false).when(taskFragment).shouldBeVisible(any());
-        display.ensureActivitiesVisible(null, 0, false, false);
+        display.ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
         assertFalse(activity.isVisibleRequested());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index c757457..09f677e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -172,7 +172,7 @@
     @Test
     public void testScheduleTransactionItemUnlocked() throws RemoteException {
         // Use non binder client to get non-recycled ClientTransaction.
-        mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+        mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
 
         // Dispatch immediately.
         assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
@@ -217,6 +217,8 @@
 
     @Test
     public void testDispatchPendingTransactions() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
         mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
 
         mLifecycleManager.dispatchPendingTransactions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfe79bf..6497ee9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -75,7 +75,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -490,7 +489,7 @@
         newOverrideConfig.fontScale += 0.3;
 
         defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
-                null /* starting */, false /* deferResume */, null /* result */);
+                null /* starting */, false /* deferResume */);
 
         // Check that global configuration is updated, as we've updated default display's config.
         Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -499,7 +498,7 @@
 
         // Return back to original values.
         defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
-                null /* starting */, false /* deferResume */, null /* result */);
+                null /* starting */, false /* deferResume */);
         globalConfig = mWm.mRoot.getConfiguration();
         assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
@@ -1176,7 +1175,7 @@
         activity.setRequestedOrientation(newOrientation);
 
         verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
-                anyBoolean(), same(null));
+                anyBoolean());
         assertEquals(ROTATION_180, dc.getRotation());
     }
 
@@ -2123,10 +2122,8 @@
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
         waitUntilHandlersIdle();
-        verify(activity1).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
-                anyBoolean());
-        verify(activity2).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
-                anyBoolean());
+        verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+        verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
@@ -2248,11 +2245,11 @@
             // The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice.
             assertFalse(called[0]);
             called[0] = true;
-            mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+            mDisplayContent.ensureActivitiesVisible(null, false);
             return null;
-        }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean());
+        }).when(mockTda).ensureActivitiesVisible(any(), anyBoolean());
 
-        mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+        mDisplayContent.ensureActivitiesVisible(null, false);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 8de45b0..32b3558 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -106,8 +106,7 @@
         topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
 
         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                 mRecentsComponent, true /* getRecentsAnimation */);
@@ -178,8 +177,7 @@
         mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
                 null /* recentsAnimationRunner */);
 
-        verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
-                anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
+        verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
     }
 
@@ -199,8 +197,7 @@
                 "testRestartRecentsActivity");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
         doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
         doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
 
@@ -354,8 +351,7 @@
 
         doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
 
         startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(),
                 true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 89cd726..b5883b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -823,8 +823,7 @@
                 .build();
 
         doReturn(false).when(secondActivity).occludesParent();
-        homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        homeRootTask.ensureActivitiesVisible(null /* starting */);
 
         assertTrue(firstActivity.shouldBeVisible());
     }
@@ -1419,8 +1418,7 @@
 
         // Any common path that updates activity visibility should clear the unknown visibility
         // records that are no longer visible according to hierarchy.
-        task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        task.ensureActivitiesVisible(null /* starting */);
         // Assume the top activity relayouted, just remove it directly.
         unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
         // All unresolved records should be removed.
@@ -1441,8 +1439,7 @@
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
                 anyBoolean());
 
-        task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        task.ensureActivitiesVisible(null /* starting */);
         verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
                 anyBoolean());
     }
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 c3102e0..5518c60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -947,7 +947,7 @@
 
         // Recompute the natural configuration in the new display.
         mActivity.clearSizeCompatMode();
-        mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        mActivity.ensureActivityConfiguration();
         // Because the display cannot rotate, the portrait activity will fit the short side of
         // display with keeping portrait bounds [200, 0 - 700, 1000] in center.
         assertEquals(newDisplayBounds.height(), currentBounds.height());
@@ -4858,7 +4858,7 @@
         }
         // Make sure to use the provided configuration to construct the size compat fields.
         activity.clearSizeCompatMode();
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
             activity.mDisplayContent.sendNewConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 51f0404..90493d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,7 +134,6 @@
     private StaticMockitoSession mMockitoSession;
     private ActivityTaskManagerService mAtmService;
     private WindowManagerService mWmService;
-    private WindowState.PowerManagerWrapper mPowerManagerWrapper;
     private InputManagerService mImService;
     private InputChannel mInputChannel;
     private Runnable mOnBeforeServicesCreated;
@@ -360,7 +359,6 @@
     }
 
     private void setUpWindowManagerService() {
-        mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
         TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy();
         TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider =
                 new TestDisplayWindowSettingsProvider();
@@ -377,8 +375,7 @@
         // Always keep things awake.
         doReturn(true).when(mWmService.mRoot).hasAwakeDisplay();
         // Called when moving activity to pinned stack.
-        doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(),
-                anyInt(), anyBoolean(), anyBoolean());
+        doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(), anyBoolean());
         spyOn(mWmService.mDisplayWindowSettings);
         spyOn(mWmService.mDisplayWindowSettingsProvider);
 
@@ -485,10 +482,6 @@
         return mAtmService;
     }
 
-    WindowState.PowerManagerWrapper getPowerManagerWrapper() {
-        return mPowerManagerWrapper;
-    }
-
     /** Creates a no-op wakelock object. */
     PowerManager.WakeLock createStubbedWakeLock(boolean needVerification) {
         if (needVerification) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index ec068be..00e22fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -269,8 +269,7 @@
         mTaskFragment.getTask().addChild(activityBelow, 0);
 
         // Ensure the activity below is visible
-        mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */);
         assertEquals(true, activityBelow.isVisibleRequested());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index da7612b..45e1e95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -339,16 +339,14 @@
         // Check visibility of occluded tasks
         doReturn(false).when(leafTask1).shouldBeVisible(any());
         doReturn(true).when(leafTask2).shouldBeVisible(any());
-        rootTask.ensureActivitiesVisible(
-                null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         assertFalse(activity1.isVisible());
         assertTrue(activity2.isVisible());
 
         // Check visibility of not occluded tasks
         doReturn(true).when(leafTask1).shouldBeVisible(any());
         doReturn(true).when(leafTask2).shouldBeVisible(any());
-        rootTask.ensureActivitiesVisible(
-                null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         assertTrue(activity1.isVisible());
         assertTrue(activity2.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bd111ad..52e2d8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -89,8 +89,8 @@
     }
 
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         return 0;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 71447e7..fddd771 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1495,8 +1495,7 @@
         verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1));
 
         enteringAnimReports.clear();
-        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
-                anyInt(), anyBoolean(), anyBoolean());
+        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
         final boolean[] wasInFinishingTransition = { false };
         controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e31ee11..400e4b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -38,7 +38,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -306,10 +305,12 @@
 
     @Test
     public void testCachedStateConfigurationChange() throws RemoteException {
-        doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
+        doNothing().when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
         final IApplicationThread thread = mWpc.getThread();
         final Configuration newConfig = new Configuration(mWpc.getConfiguration());
         newConfig.densityDpi += 100;
+        mWpc.mWindowSession = getTestSession();
+        mWpc.mWindowSession.onWindowAdded(mock(WindowState.class));
         // Non-cached state will send the change directly.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         clearInvocations(mClientLifecycleManager);
@@ -322,13 +323,13 @@
         newConfig.densityDpi += 100;
         mWpc.onConfigurationChanged(newConfig);
         verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
-        verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItemNow(eq(thread), any());
 
         // Cached -> non-cached will send the previous deferred config immediately.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
         final ArgumentCaptor<ConfigurationChangeItem> captor =
                 ArgumentCaptor.forClass(ConfigurationChangeItem.class);
-        verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+        verify(mClientLifecycleManager).scheduleTransactionItemNow(
                 eq(thread), captor.capture());
         final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
         captor.getValue().preExecute(client);
@@ -432,7 +433,7 @@
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
                 GRAMMATICAL_GENDER_NOT_SPECIFIED);
-        verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
+        verify(activity).ensureActivityConfiguration();
     }
 
     @Test
@@ -443,7 +444,7 @@
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
                 GRAMMATICAL_GENDER_NOT_SPECIFIED);
         verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
-        verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
+        verify(activity, never()).ensureActivityConfiguration();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 2007f68..75e252f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -52,7 +52,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -368,28 +367,26 @@
         firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
         secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
 
-        final WindowState.PowerManagerWrapper powerManagerWrapper =
-                mSystemServicesTestRule.getPowerManagerWrapper();
-        reset(powerManagerWrapper);
+        final var powerManager = mWm.mPowerManager;
+        clearInvocations(powerManager);
         firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
-        verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+        verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
 
-        reset(powerManagerWrapper);
+        clearInvocations(powerManager);
         secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
-        verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+        verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
     }
 
     private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
             boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) {
-        final WindowState.PowerManagerWrapper powerManagerWrapper =
-                mSystemServicesTestRule.getPowerManagerWrapper();
-        reset(powerManagerWrapper);
+        final var powerManager = mWm.mPowerManager;
+        clearInvocations(powerManager);
         appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
 
         if (expectedWakeupCalled) {
-            verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+            verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
         } else {
-            verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
+            verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
         }
         // If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
         // because the state will be consumed.
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 616a23e..a5f6190f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -637,14 +637,12 @@
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
         return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
-                ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow,
-                mSystemServicesTestRule.getPowerManagerWrapper());
+                ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token,
             String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
-            WindowManagerService service, Session session, IWindow iWindow,
-            WindowState.PowerManagerWrapper powerManagerWrapper) {
+            WindowManagerService service, Session session, IWindow iWindow) {
         SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
@@ -652,9 +650,7 @@
         attrs.packageName = "test";
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
-                OP_NONE, attrs, VISIBLE, ownerId, userId,
-                ownerCanAddInternalSystemWindow,
-                powerManagerWrapper);
+                OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
         token.addWindow(w);
@@ -1738,17 +1734,14 @@
     static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer {
         private final ActivityTaskManagerService mAtm;
         private final WindowManagerService mWMService;
-        private final WindowState.PowerManagerWrapper mPowerManagerWrapper;
 
         private Runnable mRunnableWhenAddingSplashScreen;
         private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
         private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
 
-        TestStartingWindowOrganizer(ActivityTaskManagerService service,
-                WindowState.PowerManagerWrapper powerManagerWrapper) {
+        TestStartingWindowOrganizer(ActivityTaskManagerService service) {
             mAtm = service;
             mWMService = mAtm.mWindowManager;
-            mPowerManagerWrapper = powerManagerWrapper;
             mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
             mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
         }
@@ -1767,8 +1760,7 @@
                 final WindowState window = WindowTestsBase.createWindow(null,
                         TYPE_APPLICATION_STARTING, activity,
                         "Starting window", 0 /* ownerId */, 0 /* userId*/,
-                        false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow,
-                        mPowerManagerWrapper);
+                        false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
                 activity.mStartingWindow = window;
                 mAppWindowMap.put(info.appToken, window);
                 mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 4c978ad..2445f51 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -28,6 +28,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.usage.ExternalStorageStats;
+import android.app.usage.Flags;
 import android.app.usage.IStorageStatsManager;
 import android.app.usage.StorageStats;
 import android.app.usage.UsageStatsManagerInternal;
@@ -434,6 +435,7 @@
         final long[] ceDataInodes = new long[packageNames.length];
         String[] codePaths = new String[0];
 
+        final PackageStats stats = new PackageStats(TAG);
         for (int i = 0; i < packageNames.length; i++) {
             try {
                 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
@@ -443,7 +445,11 @@
                 } else {
                     if (appInfo.getCodePath() != null) {
                         codePaths = ArrayUtils.appendElement(String.class, codePaths,
-                                appInfo.getCodePath());
+                            appInfo.getCodePath());
+                    }
+                    if (Flags.getAppBytesByDataTypeApi()) {
+                        computeAppStatsByDataTypes(
+                            stats, appInfo.sourceDir);
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -451,7 +457,6 @@
             }
         }
 
-        final PackageStats stats = new PackageStats(TAG);
         try {
             mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
                     appId, ceDataInodes, codePaths, stats);
@@ -587,6 +592,9 @@
         res.codeBytes = stats.codeSize + stats.externalCodeSize;
         res.dataBytes = stats.dataSize + stats.externalDataSize;
         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+        res.apkBytes = stats.apkSize;
+        res.libBytes = stats.libSize;
+        res.dmBytes = stats.dmSize;
         res.externalCacheBytes = stats.externalCacheSize;
         return res;
     }
@@ -894,4 +902,61 @@
             mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
         }
     }
+
+    private long getDirBytes(File dir) {
+        if (!dir.isDirectory()) {
+            return 0;
+        }
+
+        long size = 0;
+        try {
+            for (File file : dir.listFiles()) {
+                if (file.isFile()) {
+                    size += file.length();
+                    continue;
+                }
+                if (file.isDirectory()) {
+                    size += getDirBytes(file);
+                }
+            }
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed to list directory " + dir.getName());
+        }
+
+        return size;
+    }
+
+    private long getFileBytesInDir(File dir, String suffix) {
+        if (!dir.isDirectory()) {
+            return 0;
+        }
+
+        long size = 0;
+        try {
+            for (File file : dir.listFiles()) {
+                if (file.isFile() && file.getName().endsWith(suffix)) {
+                    size += file.length();
+                }
+            }
+        } catch (NullPointerException e) {
+             Slog.w(TAG, "Failed to list directory " + dir.getName());
+        }
+
+        return size;
+    }
+
+    private void computeAppStatsByDataTypes(
+        PackageStats stats, String sourceDirName) {
+
+        // Get apk, lib, dm file sizes.
+        File srcDir = new File(sourceDirName);
+        if (srcDir.isFile()) {
+            sourceDirName = srcDir.getParent();
+            srcDir = new File(sourceDirName);
+        }
+
+        stats.apkSize += getFileBytesInDir(srcDir, ".apk");
+        stats.dmSize += getFileBytesInDir(srcDir, ".dm");
+        stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ccd4ce0..08f719e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1121,13 +1121,8 @@
 
             switch (event.mEventType) {
                 case Event.ACTIVITY_RESUMED:
-                    FrameworkStatsLog.write(
-                            FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
-                            uid,
-                            event.mPackage,
-                            "",
-                            FrameworkStatsLog
-                                    .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND);
+                    logAppUsageEventReportedAtomLocked(Event.ACTIVITY_RESUMED, uid, event.mPackage);
+
                     // check if this activity has already been resumed
                     if (mVisibleActivities.get(event.mInstanceId) != null) break;
                     final String usageSourcePackage = getUsageSourcePackage(event);
@@ -1172,13 +1167,8 @@
                                 usageSourcePackage2);
                         mVisibleActivities.put(event.mInstanceId, pausedData);
                     } else {
-                        FrameworkStatsLog.write(
-                                FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
-                                uid,
-                                event.mPackage,
-                                "",
-                                FrameworkStatsLog
-                                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+                        logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+                                event.mPackage);
                     }
 
                     pausedData.lastEvent = Event.ACTIVITY_PAUSED;
@@ -1203,13 +1193,8 @@
                     }
 
                     if (prevData.lastEvent != Event.ACTIVITY_PAUSED) {
-                        FrameworkStatsLog.write(
-                                FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
-                                uid,
-                                event.mPackage,
-                                "",
-                                FrameworkStatsLog
-                                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+                        logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+                                event.mPackage);
                     }
 
                     ArraySet<String> tokens;
@@ -1244,11 +1229,19 @@
                     }
                     break;
                 case Event.USER_INTERACTION:
-                    // Fall through
+                    logAppUsageEventReportedAtomLocked(Event.USER_INTERACTION, uid, event.mPackage);
+                    // Fall through.
                 case Event.APP_COMPONENT_USED:
                     convertToSystemTimeLocked(event);
                     mLastTimeComponentUsedGlobal.put(event.mPackage, event.mTimeStamp);
                     break;
+                case Event.SHORTCUT_INVOCATION:
+                case Event.CHOOSER_ACTION:
+                case Event.STANDBY_BUCKET_CHANGED:
+                case Event.FOREGROUND_SERVICE_START:
+                case Event.FOREGROUND_SERVICE_STOP:
+                    logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
+                    break;
             }
 
             final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
@@ -1261,6 +1254,45 @@
         mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget();
     }
 
+    @GuardedBy("mLock")
+    private void logAppUsageEventReportedAtomLocked(int eventType, int uid, String packageName) {
+        FrameworkStatsLog.write(FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, uid, packageName,
+                "", getAppUsageEventOccurredAtomEventType(eventType));
+    }
+
+    /** Make sure align with the EventType defined in the AppUsageEventOccurred atom. */
+    private int getAppUsageEventOccurredAtomEventType(int eventType) {
+        switch (eventType) {
+            case Event.ACTIVITY_RESUMED:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND;
+            case Event.ACTIVITY_PAUSED:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND;
+            case Event.USER_INTERACTION:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__USER_INTERACTION;
+            case Event.SHORTCUT_INVOCATION:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__SHORTCUT_INVOCATION;
+            case Event.CHOOSER_ACTION:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__CHOOSER_ACTION;
+            case Event.STANDBY_BUCKET_CHANGED:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__STANDBY_BUCKET_CHANGED;
+            case Event.FOREGROUND_SERVICE_START:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_START;
+            case Event.FOREGROUND_SERVICE_STOP:
+                return FrameworkStatsLog
+                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_STOP;
+            default:
+                Slog.w(TAG, "Unsupported usage event logging: " + eventType);
+                return -1;
+        }
+    }
+
     private String getUsageSourcePackage(Event event) {
         switch(mUsageSource) {
             case USAGE_SOURCE_CURRENT_ACTIVITY:
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 1dc5dcf..3a0a6ab 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -35,4 +35,7 @@
         "android.hardware.usb-V1.3-java",
         "android.hardware.usb-V3-java",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/telecomm/java/android/telecom/AuthenticatorService.java b/telecomm/java/android/telecom/AuthenticatorService.java
deleted file mode 100644
index 1e43c71..0000000
--- a/telecomm/java/android/telecom/AuthenticatorService.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 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.telecom;
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.NetworkErrorException;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-
-/**
- * A generic stub account authenticator service often used for sync adapters that do not directly
- * involve accounts.
- *
- * @hide
- */
-public class AuthenticatorService extends Service {
-    private static Authenticator mAuthenticator;
-
-    @Override
-    public void onCreate() {
-        mAuthenticator = new Authenticator(this);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mAuthenticator.getIBinder();
-    }
-
-    /**
-     * Stub account authenticator. All methods either return null or throw an exception.
-     */
-    public class Authenticator extends AbstractAccountAuthenticator {
-        public Authenticator(Context context) {
-            super(context);
-        }
-
-        @Override
-        public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                     String s) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                 String s, String s2, String[] strings, Bundle bundle)
-                throws NetworkErrorException {
-            return null;
-        }
-
-        @Override
-        public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                         Account account, Bundle bundle)
-                throws NetworkErrorException {
-            return null;
-        }
-
-        @Override
-        public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                   Account account, String s, Bundle bundle)
-                throws NetworkErrorException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public String getAuthTokenLabel(String s) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                        Account account, String s, Bundle bundle)
-                throws NetworkErrorException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
-                                  Account account, String[] strings)
-                throws NetworkErrorException {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ee9bf898..a2105b0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3340,15 +3340,6 @@
     public void onDisconnect() {}
 
     /**
-     * Notifies this Connection of a request to disconnect a participant of the conference managed
-     * by the connection.
-     *
-     * @param endpoint the {@link Uri} of the participant to disconnect.
-     * @hide
-     */
-    public void onDisconnectConferenceParticipant(Uri endpoint) {}
-
-    /**
      * Notifies this Connection of a request to separate from its parent conference.
      */
     public void onSeparate() {}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fb213a7..c7b84a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3721,19 +3721,19 @@
      * This configuration allows the system UI to display different 5G icons for different 5G
      * scenarios.
      *
-     * There are five 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
+     * There are six 5G scenarios for icon configuration:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
      *
      * The configured string contains multiple key-value pairs separated by comma. For each pair,
      * the key and value are separated by a colon. The key corresponds to a 5G status above and
@@ -3754,21 +3754,21 @@
      * This configuration allows the system UI to determine how long to continue to display 5G icons
      * when the device switches between different 5G scenarios.
      *
-     * There are seven 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
-     * 6. legacy: device is not camped on a network that has 5G capability
-     * 7. any: any of the above scenarios
+     * There are eight 5G scenarios:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
+     * 7. legacy: device is not camped on a network that has 5G capability
+     * 8. any: any of the above scenarios
      *
      * The configured string contains various timer rules separated by a semicolon.
      * Each rule will have three items: prior 5G scenario, current 5G scenario, and grace period
@@ -3776,8 +3776,8 @@
      * 5G scenario, the system UI will continue to show the icon for the prior 5G scenario (defined
      * in {@link #KEY_5G_ICON_CONFIGURATION_STRING}) for the amount of time specified by the grace
      * period. If the prior 5G scenario is reestablished, the timer will reset and start again if
-     * the UE changes 5G scenarios again. Defined states (5G scenarios #1-5) take precedence over
-     * 'any' (5G scenario #6), and unspecified transitions have a default grace period of 0.
+     * the UE changes 5G scenarios again. Defined states (5G scenarios #1-7) take precedence over
+     * 'any' (5G scenario #8), and unspecified transitions have a default grace period of 0.
      * The order of rules in the configuration determines the priority (the first applicable timer
      * rule will be used).
      *
@@ -3800,21 +3800,21 @@
      * This configuration extends {@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING} to allow the
      * system UI to continue displaying 5G icons after the initial timer expires.
      *
-     * There are seven 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
-     * 6. legacy: device is not camped on a network that has 5G capability
-     * 7. any: any of the above scenarios
+     * There are eight 5G scenarios:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
+     * 7. legacy: device is not camped on a network that has 5G capability
+     * 8. any: any of the above scenarios
      *
      * The configured string contains various timer rules separated by a semicolon.
      * Each rule will have three items: primary 5G scenario, secondary 5G scenario, and
@@ -3824,7 +3824,7 @@
      * period. If the primary 5G scenario is reestablished, the timers will reset and the system UI
      * will continue to display the icon for the primary 5G scenario without interruption. If the
      * secondary 5G scenario is lost, the timer will reset and the icon will reflect the true state.
-     * Defined states (5G scenarios #1-5) take precedence over 'any' (5G scenario #6), and
+     * Defined states (5G scenarios #1-7) take precedence over 'any' (5G scenario #8), and
      * unspecified transitions have a default grace period of 0. The order of rules in the
      * configuration determines the priority (the first applicable timer rule will be used).
      *
@@ -8891,18 +8891,18 @@
                 KEY_PREFIX + "epdg_static_address_roaming_string";
 
         /**
-         * Controls if the multiple SA proposals allowed for IKE session to include
-         * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
-         * IKE SA proposals as per RFC 7296.
+         * Enables the use of multiple IKE SA proposals, encompassing both carrier-preferred
+         * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+         * as defined in RFC 7296.
          */
         @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
         public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
                 KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool";
 
         /**
-         * Controls if the multiple SA proposals allowed for Child session to include
-         * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
-         * Child SA proposals as per RFC 7296.
+         * Enables the use of multiple Child SA proposals, encompassing both carrier-preferred
+         * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+         * as defined in RFC 7296.
          */
         @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
         public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
@@ -10608,7 +10608,7 @@
         sDefaults.putBoolean(KEY_USE_CALL_WAITING_USSD_BOOL, false);
         sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
         sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING,
-                "connected_mmwave:5G,connected:5G,not_restricted_rrc_idle:5G,"
+                "connected_mmwave:5G,connected:5G,connected_rrc_idle:5G,not_restricted_rrc_idle:5G,"
                         + "not_restricted_rrc_con:5G");
         sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "");
         sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
new file mode 100644
index 0000000..61d7ead
--- /dev/null
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A single occurrence capturing a notable change to previously reported
+ * cryptography algorithms for a given network and network event.
+ *
+ * @hide
+ */
+public final class SecurityAlgorithmUpdate implements Parcelable {
+    private static final String TAG = "SecurityAlgorithmUpdate";
+
+    private @ConnectionEvent int mConnectionEvent;
+    private @SecurityAlgorithm int mEncryption;
+    private @SecurityAlgorithm int mIntegrity;
+    private boolean mIsUnprotectedEmergency;
+
+    public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent,
+            @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity,
+            boolean isUnprotectedEmergency) {
+        mConnectionEvent = connectionEvent;
+        mEncryption = encryption;
+        mIntegrity = integrity;
+        mIsUnprotectedEmergency = isUnprotectedEmergency;
+    }
+
+    private SecurityAlgorithmUpdate(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public @ConnectionEvent int getConnectionEvent() {
+        return mConnectionEvent;
+    }
+
+    public @SecurityAlgorithm int getEncryption() {
+        return mEncryption;
+    }
+
+    public @SecurityAlgorithm int getIntegrity() {
+        return mIntegrity;
+    }
+
+    public boolean isUnprotectedEmergency() {
+        return mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mConnectionEvent);
+        out.writeInt(mEncryption);
+        out.writeInt(mIntegrity);
+        out.writeBoolean(mIsUnprotectedEmergency);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mConnectionEvent = in.readInt();
+        mEncryption = in.readInt();
+        mIntegrity = in.readInt();
+        mIsUnprotectedEmergency = in.readBoolean();
+    }
+
+    public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR =
+            new Parcelable.Creator<SecurityAlgorithmUpdate>() {
+                public SecurityAlgorithmUpdate createFromParcel(Parcel in) {
+                    return new SecurityAlgorithmUpdate(in);
+                }
+
+                public SecurityAlgorithmUpdate[] newArray(int size) {
+                    return new SecurityAlgorithmUpdate[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption
+                + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = "
+                + mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SecurityAlgorithmUpdate)) return false;
+        SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o;
+        return mConnectionEvent == that.mConnectionEvent
+                && mEncryption == that.mEncryption
+                && mIntegrity == that.mIntegrity
+                && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency);
+    }
+
+    public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0;
+    public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1;
+    public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2;
+    public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
+    public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
+    public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
+    public static final int CONNECTION_EVENT_VONR_SIP = 10;
+    public static final int CONNECTION_EVENT_VONR_RTP = 11;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {CONNECTION_EVENT_CS_SIGNALLING_GSM,
+            CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
+            CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
+            CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
+            CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+            CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
+            CONNECTION_EVENT_VONR_RTP})
+    public @interface ConnectionEvent {
+    }
+
+    public static final int SECURITY_ALGORITHM_A50 = 0;
+    public static final int SECURITY_ALGORITHM_A51 = 1;
+    public static final int SECURITY_ALGORITHM_A52 = 2;
+    public static final int SECURITY_ALGORITHM_A53 = 3;
+    public static final int SECURITY_ALGORITHM_A54 = 4;
+    public static final int SECURITY_ALGORITHM_GEA0 = 14;
+    public static final int SECURITY_ALGORITHM_GEA1 = 15;
+    public static final int SECURITY_ALGORITHM_GEA2 = 16;
+    public static final int SECURITY_ALGORITHM_GEA3 = 17;
+    public static final int SECURITY_ALGORITHM_GEA4 = 18;
+    public static final int SECURITY_ALGORITHM_GEA5 = 19;
+    public static final int SECURITY_ALGORITHM_UEA0 = 29;
+    public static final int SECURITY_ALGORITHM_UEA1 = 30;
+    public static final int SECURITY_ALGORITHM_UEA2 = 31;
+    public static final int SECURITY_ALGORITHM_EEA0 = 41;
+    public static final int SECURITY_ALGORITHM_EEA1 = 42;
+    public static final int SECURITY_ALGORITHM_EEA2 = 43;
+    public static final int SECURITY_ALGORITHM_EEA3 = 44;
+    public static final int SECURITY_ALGORITHM_NEA0 = 55;
+    public static final int SECURITY_ALGORITHM_NEA1 = 56;
+    public static final int SECURITY_ALGORITHM_NEA2 = 57;
+    public static final int SECURITY_ALGORITHM_NEA3 = 58;
+    public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
+    public static final int SECURITY_ALGORITHM_AES_GCM = 69;
+    public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
+    public static final int SECURITY_ALGORITHM_AES_CBC = 71;
+    public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
+    public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
+    public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
+    public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
+    public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
+    public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+    public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
+    public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
+    public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
+    public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99;
+    public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100;
+    public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101;
+    public static final int SECURITY_ALGORITHM_UNKNOWN = 113;
+    public static final int SECURITY_ALGORITHM_OTHER = 114;
+    public static final int SECURITY_ALGORITHM_ORYX = 124;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {SECURITY_ALGORITHM_A50, SECURITY_ALGORITHM_A51,
+            SECURITY_ALGORITHM_A52, SECURITY_ALGORITHM_A53,
+            SECURITY_ALGORITHM_A54, SECURITY_ALGORITHM_GEA0, SECURITY_ALGORITHM_GEA1,
+            SECURITY_ALGORITHM_GEA2, SECURITY_ALGORITHM_GEA3, SECURITY_ALGORITHM_GEA4,
+            SECURITY_ALGORITHM_GEA5, SECURITY_ALGORITHM_UEA0, SECURITY_ALGORITHM_UEA1,
+            SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
+            SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
+            SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
+            SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+            SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
+            SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
+            SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
+            SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
+            SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+            SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+            SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+            SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+    public @interface SecurityAlgorithm {
+    }
+
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 22a5cd7..9e292be 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2115,6 +2115,20 @@
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
             "com.android.omadm.service.CONFIGURATION_UPDATE";
 
+    /**
+     * Activity action: Show setting to reset mobile networks.
+     *
+     * <p>On devices with a settings activity to reset mobile networks, the activity should be
+     * launched without additional permissions.
+     *
+     * <p>On some devices, this settings activity may not exist. Callers should ensure that this
+     * case is appropriately handled.
+     */
+    @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS)
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS =
+            "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
+
     //
     //
     // Device Info
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208d..5cbb1aa 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@
             .autoFix()
             .build()
 
-        return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+        return LintFix.create()
+            .name(annotateFix.getDisplayName())
+            .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
     }
 
     private val annotation: String